Merge branch 'torxed-v2.2.0' into torxed-v2.2.0
This commit is contained in:
commit
f5ef751d53
|
|
@ -0,0 +1,37 @@
|
||||||
|
# This workflow will build an Arch Linux ISO file with the commit on it
|
||||||
|
|
||||||
|
name: Build Arch ISO with ArchInstall Commit
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- '.github/**'
|
||||||
|
- 'docs/**'
|
||||||
|
- '**.editorconfig'
|
||||||
|
- '**.gitignore'
|
||||||
|
- '**.md'
|
||||||
|
- 'LICENSE'
|
||||||
|
- 'PKGBUILD'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: archlinux:latest
|
||||||
|
options: --privileged
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- run: pwd
|
||||||
|
- run: find .
|
||||||
|
- run: cat /etc/os-release
|
||||||
|
- run: mkdir -p /tmp/archlive/airootfs/root/archinstall-git; cp -r . /tmp/archlive/airootfs/root/archinstall-git
|
||||||
|
- run: echo "pip uninstall archinstall -y; cd archinstall-git; python setup.py install; echo 'Type python -m archinstall to launch archinstall'" > /tmp/archlive/airootfs/root/.zprofile
|
||||||
|
- run: pacman -Sy; pacman --noconfirm -S git archiso
|
||||||
|
- run: cp -r /usr/share/archiso/configs/releng/* /tmp/archlive
|
||||||
|
- run: echo -e "git\npython\npython-pip\npython-setuptools" >> /tmp/archlive/packages.x86_64
|
||||||
|
- run: find /tmp/archlive
|
||||||
|
- run: cd /tmp/archlive; mkarchiso -v -w work/ -o out/ ./
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: Arch Live ISO
|
||||||
|
path: /tmp/archlive/out/*.iso
|
||||||
|
|
@ -21,11 +21,10 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install setuptools wheel twine
|
pip install setuptools wheel flit
|
||||||
- name: Build and publish
|
- name: Build and publish
|
||||||
env:
|
env:
|
||||||
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
FLIT_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
||||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
FLIT_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||||
run: |
|
run: |
|
||||||
python setup.py sdist bdist_wheel
|
flit publish
|
||||||
twine upload dist/*
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
[distutils]
|
||||||
|
index-servers =
|
||||||
|
pypi
|
||||||
|
|
||||||
|
[pypi]
|
||||||
|
repository = https://upload.pypi.org/legacy/
|
||||||
|
|
@ -6,7 +6,7 @@ Therefore guidelines and style changes to the code might come into affect as wel
|
||||||
|
|
||||||
## Discussions
|
## Discussions
|
||||||
|
|
||||||
Currently, questions, bugs and suggestions should be reported through [GitHub issue tracker](https://github.com/Torxed/archinstall/issues).<br>
|
Currently, questions, bugs and suggestions should be reported through [GitHub issue tracker](https://github.com/archlinux/archinstall/issues).<br>
|
||||||
For less formal discussions there are also a [archinstall Discord server](https://discord.gg/cqXU88y).
|
For less formal discussions there are also a [archinstall Discord server](https://discord.gg/cqXU88y).
|
||||||
|
|
||||||
## Coding convention
|
## Coding convention
|
||||||
|
|
|
||||||
35
PKGBUILD
35
PKGBUILD
|
|
@ -2,43 +2,24 @@
|
||||||
# Contributor: Giancarlo Razzolini <grazzolini@archlinux.org>
|
# Contributor: Giancarlo Razzolini <grazzolini@archlinux.org>
|
||||||
# Contributor: demostanis worlds <demostanis@protonmail.com>
|
# Contributor: demostanis worlds <demostanis@protonmail.com>
|
||||||
|
|
||||||
pkgbase=archinstall-git
|
pkgname=archinstall-git
|
||||||
pkgname=('archinstall-git' 'python-archinstall-git')
|
|
||||||
pkgver=$(git describe --long | sed 's/\([^-]*-g\)/r\1/;s/-/./g')
|
pkgver=$(git describe --long | sed 's/\([^-]*-g\)/r\1/;s/-/./g')
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Just another guided/automated Arch Linux installer with a twist"
|
pkgdesc="Just another guided/automated Arch Linux installer with a twist"
|
||||||
arch=('any')
|
arch=('any')
|
||||||
url="https://github.com/Torxed/archinstall"
|
url="https://github.com/archlinux/archinstall"
|
||||||
license=('GPL')
|
license=('GPL')
|
||||||
depends=('python')
|
depends=('python')
|
||||||
makedepends=('python-setuptools')
|
makedepends=('python-setuptools')
|
||||||
|
provides=('python-archinstall')
|
||||||
|
conflicts=('archinstall' 'python-archinstall' 'python-archinstall-git')
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "$startdir"
|
cd "$startdir"
|
||||||
|
|
||||||
python setup.py build
|
python setup.py build
|
||||||
}
|
}
|
||||||
|
|
||||||
|
package() {
|
||||||
package_archinstall-git() {
|
cd "$startdir"
|
||||||
depends=('python-archinstall-git')
|
python setup.py install --root="${pkgdir}" --optimize=1 --skip-build
|
||||||
conflicts=('archinstall')
|
|
||||||
cd "$startdir"
|
|
||||||
|
|
||||||
mkdir -p "${pkgdir}/usr/bin"
|
|
||||||
|
|
||||||
# Install a guided profile
|
|
||||||
cat - > "${pkgdir}/usr/bin/archinstall" <<EOF
|
|
||||||
#!/bin/sh
|
|
||||||
python -m archinstall $@
|
|
||||||
EOF
|
|
||||||
|
|
||||||
chmod +x "${pkgdir}/usr/bin/archinstall"
|
|
||||||
}
|
|
||||||
|
|
||||||
package_python-archinstall-git() {
|
|
||||||
conflicts=('python-archinstall')
|
|
||||||
cd "$startdir"
|
|
||||||
|
|
||||||
python setup.py install --prefix=/usr --root="${pkgdir}" --optimize=1 --skip-build
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
# Maintainer: Anton Hvornum anton@hvornum.se
|
|
||||||
# Contributor: Anton Hvornum anton@hvornum.se
|
|
||||||
|
|
||||||
pkgname="archinstall-bin"
|
|
||||||
pkgver="2.1.3"
|
|
||||||
pkgdesc="Installs a pre-built binary of ${pkgname}"
|
|
||||||
pkgrel=1
|
|
||||||
url="https://github.com/Torxed/archinstall"
|
|
||||||
license=('GPLv3')
|
|
||||||
provides=("${pkgname}")
|
|
||||||
arch=('x86_64')
|
|
||||||
source=("${pkgname}-v${pkgver}-x86_64.tar.gz::https://github.com/Torxed/archinstall/archive/v$pkgver.tar.gz")
|
|
||||||
#depends=('python>=3.8')
|
|
||||||
makedepends=('python>=3.8' 'nuitka')
|
|
||||||
optdepends=('pyttsx3: Adds text-to-speach support for log/screen output.')
|
|
||||||
sha256sums=('53c00f7e7ad245cd2cbbf041b5a735df2fc29454c24b1d369f678cc0610b7cea')
|
|
||||||
|
|
||||||
build() {
|
|
||||||
cd "${pkgname}-${pkgver}"
|
|
||||||
|
|
||||||
nuitka3 --standalone --show-progress archinstall
|
|
||||||
cp -r examples/ archinstall.dist/
|
|
||||||
}
|
|
||||||
|
|
||||||
package() {
|
|
||||||
echo "${srcdir}"
|
|
||||||
cd "${pkgname}-${pkgver}"
|
|
||||||
|
|
||||||
mkdir -p "${pkgdir}/var/lib/archinstall/"
|
|
||||||
mkdir -p "${pkgdir}/usr/bin"
|
|
||||||
|
|
||||||
mv archinstall.dist/* "${pkgdir}/var/lib/archinstall/"
|
|
||||||
|
|
||||||
echo '#!/bin/bash' > "${pkgdir}/usr/bin/archinstall-bin"
|
|
||||||
echo '(cd /var/lib/archinstall && exec ./archinstall)' >> "${pkgdir}/usr/bin/archinstall-bin"
|
|
||||||
|
|
||||||
chmod +x "${pkgdir}/var/lib/archinstall/archinstall"
|
|
||||||
chmod +x "${pkgdir}/usr/bin/archinstall-bin"
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Maintainer: Anton Hvornum <anton@hvornum.se>
|
|
||||||
# Contributor: demostanis worlds <demostanis@protonmail.com>
|
|
||||||
|
|
||||||
pkgname="archinstall"
|
|
||||||
pkgver="2.1.3"
|
|
||||||
pkgdesc="Installs launcher scripts for archinstall"
|
|
||||||
pkgrel=1
|
|
||||||
url="https://github.com/Torxed/archinstall"
|
|
||||||
license=('GPLv3')
|
|
||||||
provides=("${pkgname}")
|
|
||||||
arch=('x86_64')
|
|
||||||
source=("${pkgname}-v${pkgver}-x86_64.tar.gz::https://github.com/Torxed/archinstall/archive/v$pkgver.tar.gz")
|
|
||||||
depends=('python-archinstall')
|
|
||||||
sha256sums=('53c00f7e7ad245cd2cbbf041b5a735df2fc29454c24b1d369f678cc0610b7cea')
|
|
||||||
|
|
||||||
package() {
|
|
||||||
mkdir -p "${pkgdir}/usr/bin"
|
|
||||||
|
|
||||||
# Install a guided profile
|
|
||||||
cat - > "${pkgdir}/usr/bin/archinstall" <<EOF
|
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
python -m archinstall $@
|
|
||||||
EOF
|
|
||||||
|
|
||||||
chmod +x "${pkgdir}/usr/bin/archinstall"
|
|
||||||
}
|
|
||||||
|
|
||||||
# vim:ft=sh
|
|
||||||
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
# Maintainer: Anton Hvornum <anton@hvornum.se>
|
|
||||||
# Contributor: demostanis worlds <demostanis@protonmail.com>
|
|
||||||
|
|
||||||
pkgname="python-archinstall"
|
|
||||||
pkgver="2.1.3"
|
|
||||||
pkgdesc="Installs ${pkgname} as a python library."
|
|
||||||
pkgrel=1
|
|
||||||
url="https://github.com/Torxed/archinstall"
|
|
||||||
source=("${pkgname}-v${pkgver}-x86_64.tar.gz::https://github.com/Torxed/archinstall/archive/v$pkgver.tar.gz")
|
|
||||||
license=('GPLv3')
|
|
||||||
provides=("${pkgname}")
|
|
||||||
arch=('x86_64')
|
|
||||||
depends=('python>=3.8')
|
|
||||||
makedepends=('python-setuptools')
|
|
||||||
optdepends=('pyttsx3: Adds text-to-speech support for log/screen output.')
|
|
||||||
sha256sums=('53c00f7e7ad245cd2cbbf041b5a735df2fc29454c24b1d369f678cc0610b7cea')
|
|
||||||
|
|
||||||
build() {
|
|
||||||
cd "archinstall-${pkgver}"
|
|
||||||
|
|
||||||
python setup.py build
|
|
||||||
|
|
||||||
# Build man pages
|
|
||||||
cd docs
|
|
||||||
make man
|
|
||||||
}
|
|
||||||
|
|
||||||
package() {
|
|
||||||
cd "archinstall-${pkgver}"
|
|
||||||
|
|
||||||
python setup.py install \
|
|
||||||
--prefix=/usr \
|
|
||||||
--root="${pkgdir}" \
|
|
||||||
--optimize=1
|
|
||||||
|
|
||||||
install -Dm644 docs/_build/man/archinstall.1 "${pkgdir}"/usr/share/man/man1/archinstall.1
|
|
||||||
}
|
|
||||||
|
|
||||||
# vim:ft=sh
|
|
||||||
|
|
||||||
85
README.md
85
README.md
|
|
@ -1,4 +1,9 @@
|
||||||
# <img src="https://github.com/Torxed/archinstall/raw/master/docs/logo.png" alt="drawing" width="200"/>
|
<!-- <div align="center"> -->
|
||||||
|
<img src="https://github.com/archlinux/archinstall/raw/master/docs/logo.png" alt="drawing" width="200"/>
|
||||||
|
|
||||||
|
# Arch Installer
|
||||||
|
<!-- </div> -->
|
||||||
|
|
||||||
Just another guided/automated [Arch Linux](https://wiki.archlinux.org/index.php/Arch_Linux) installer with a twist.
|
Just another guided/automated [Arch Linux](https://wiki.archlinux.org/index.php/Arch_Linux) installer with a twist.
|
||||||
The installer also doubles as a python library to install Arch Linux and manage services, packages and other things inside the installed system *(Usually from a live medium)*.
|
The installer also doubles as a python library to install Arch Linux and manage services, packages and other things inside the installed system *(Usually from a live medium)*.
|
||||||
|
|
||||||
|
|
@ -21,6 +26,18 @@ Assuming you are on a Arch Linux live-ISO and booted into EFI mode.
|
||||||
|
|
||||||
# python -m archinstall guided
|
# python -m archinstall guided
|
||||||
|
|
||||||
|
# Mission Statement
|
||||||
|
|
||||||
|
Archinstall promises to ship a [guided installer](https://github.com/archlinux/archinstall/blob/master/examples/guided.py) that follows the [Arch 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 will provide user friendly options along the way, but the keyword here is options, they are optional and will never be forced upon anyone. The guided installer itself is also optional to use if so desired and not forced upon anyone.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Archinstall has one fundamental function which is to be a flexible library to manage services, packages and other aspects inside the installed system. This library is in turn used by the provided guided installer but is also for anyone who wants to script their own installations.
|
||||||
|
|
||||||
|
Therefore, Archinstall will try its best to not introduce any breaking changes except for major releases which may break backwards compability after notifying about such changes.
|
||||||
|
|
||||||
# Scripting your own installation
|
# Scripting your own installation
|
||||||
|
|
||||||
You could just copy [guided.py](examples/guided.py) as a starting point.
|
You could just copy [guided.py](examples/guided.py) as a starting point.
|
||||||
|
|
@ -35,23 +52,44 @@ import archinstall, getpass
|
||||||
harddrive = archinstall.select_disk(archinstall.all_disks())
|
harddrive = archinstall.select_disk(archinstall.all_disks())
|
||||||
disk_password = getpass.getpass(prompt='Disk password (won\'t echo): ')
|
disk_password = getpass.getpass(prompt='Disk password (won\'t echo): ')
|
||||||
|
|
||||||
|
# We disable safety precautions in the library that protects the partitions
|
||||||
|
harddrive.keep_partitions = False
|
||||||
|
|
||||||
|
# First, we configure the basic filesystem layout
|
||||||
with archinstall.Filesystem(harddrive, archinstall.GPT) as fs:
|
with archinstall.Filesystem(harddrive, archinstall.GPT) as fs:
|
||||||
# use_entire_disk() is a helper to not have to format manually
|
# We create a filesystem layout that will use the entire drive
|
||||||
fs.use_entire_disk('luks2')
|
# (this is a helper function, you can partition manually as well)
|
||||||
|
fs.use_entire_disk(root_filesystem_type='btrfs')
|
||||||
|
|
||||||
harddrive.partition[0].format('fat32')
|
boot = fs.find_partition('/boot')
|
||||||
with archinstall.luks2(harddrive.partition[1], 'luksloop', disk_password) as unlocked_device:
|
root = fs.find_partition('/')
|
||||||
unlocked_device.format('btrfs')
|
|
||||||
|
|
||||||
with archinstall.Installer(unlocked_device, hostname='testmachine') as installation:
|
boot.format('vfat')
|
||||||
if installation.minimal_installation():
|
|
||||||
installation.add_bootloader(harddrive.partition[0])
|
|
||||||
|
|
||||||
installation.add_additional_packages(['nano', 'wget', 'git'])
|
# Set the flag for encrypted to allow for encryption and then encrypt
|
||||||
installation.install_profile('awesome')
|
root.encrypted = True
|
||||||
|
root.encrypt(password=disk_password)
|
||||||
|
|
||||||
|
with archinstall.luks2(root, 'luksloop', disk_password) as unlocked_root:
|
||||||
|
unlocked_root.format(root.filesystem)
|
||||||
|
unlocked_root.mount('/mnt')
|
||||||
|
|
||||||
|
boot.mount('/mnt/boot')
|
||||||
|
|
||||||
|
with archinstall.Installer('/mnt') as installation:
|
||||||
|
if installation.minimal_installation():
|
||||||
|
installation.set_hostname('minimal-arch')
|
||||||
|
installation.add_bootloader()
|
||||||
|
|
||||||
|
installation.add_additional_packages(['nano', 'wget', 'git'])
|
||||||
|
|
||||||
|
# Optionally, install a profile of choice.
|
||||||
|
# In this case, we install a minimal profile that is empty
|
||||||
|
installation.install_profile('minimal')
|
||||||
|
|
||||||
|
installation.user_create('devel', 'devel')
|
||||||
|
installation.user_set_pw('root', 'airoot')
|
||||||
|
|
||||||
installation.user_create('anton', 'test')
|
|
||||||
installation.user_set_pw('root', 'toor')
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This installer will perform the following:
|
This installer will perform the following:
|
||||||
|
|
@ -62,7 +100,7 @@ This installer will perform the following:
|
||||||
* Installs a basic instance of Arch Linux *(base base-devel linux linux-firmware btrfs-progs efibootmgr)*
|
* Installs a basic instance of Arch Linux *(base base-devel linux linux-firmware btrfs-progs efibootmgr)*
|
||||||
* Installs and configures a bootloader to partition 0 on uefi. on bios it sets the root to partition 0.
|
* Installs and configures a bootloader to partition 0 on uefi. on bios it sets the root to partition 0.
|
||||||
* Install additional packages *(nano, wget, git)*
|
* Install additional packages *(nano, wget, git)*
|
||||||
* Installs a network-profile called [awesome](https://github.com/Torxed/archinstall/blob/master/profiles/awesome.py) *(more on network profiles in the documentation)*
|
* Installs a profile with a window manager called [awesome](https://github.com/archlinux/archinstall/blob/master/profiles/awesome.py) *(more on profile installations in the [documentation](https://python-archinstall.readthedocs.io/en/latest/archinstall/Profile.html))*.
|
||||||
|
|
||||||
> **Creating your own ISO with this script on it:** 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.
|
> **Creating your own ISO with this script on it:** 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.
|
||||||
|
|
||||||
|
|
@ -73,6 +111,23 @@ When doing so, attach any `install-session_*.log` to the issue ticket which can
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
|
|
||||||
|
## Using a Live ISO Image
|
||||||
|
|
||||||
|
If you want to test a commit, branch or bleeding edge release from the repository using the vanilla Arch Live ISO image, you can replace the version of archinstall with a new version and run that with the steps described below.
|
||||||
|
|
||||||
|
1. You need a working network connection
|
||||||
|
2. Install the build requirements with `pacman -Sy; pacman -S git python-pip`
|
||||||
|
*(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 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 torxed-v2.2.0`*
|
||||||
|
6. Build the project and install it using `python setup.py install`
|
||||||
|
|
||||||
|
After this, running archinstall with `python -m archinstall` will run against whatever branch you chose in step 5.
|
||||||
|
|
||||||
|
## 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>
|
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:
|
This can be done by installing `pacman -S arch-install-scripts util-linux` locally and doing the following:
|
||||||
|
|
||||||
|
|
@ -87,5 +142,5 @@ This will create a *5GB* `testimage.img` and create a loop device which we can u
|
||||||
`archinstall` is installed and executed in [guided mode](#docs-todo). Once the installation is complete,<br>
|
`archinstall` is installed and executed in [guided mode](#docs-todo). Once the installation is complete,<br>
|
||||||
~~you can use qemu/kvm to boot the test media.~~ *(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 gives you a general idea of what we're going for here)*
|
~~you can use qemu/kvm to boot the test media.~~ *(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 gives you a general idea of what we're going for here)*
|
||||||
|
|
||||||
There's also a [Building and Testing](https://github.com/Torxed/archinstall/wiki/Building-and-Testing) guide.<br>
|
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.
|
It will go through everything from packaging, building and running *(with qemu)* the installer against a dev branch.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# This __init__ file is just here to support the
|
|
||||||
# use of archinstall as a git submodule.
|
|
||||||
from .archinstall import *
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
"""Arch Linux installer - guided, templates etc."""
|
||||||
from .lib.general import *
|
from .lib.general import *
|
||||||
from .lib.disk import *
|
from .lib.disk import *
|
||||||
from .lib.user_interaction import *
|
from .lib.user_interaction import *
|
||||||
|
|
@ -14,6 +15,8 @@ from .lib.output import *
|
||||||
from .lib.storage import *
|
from .lib.storage import *
|
||||||
from .lib.hardware import *
|
from .lib.hardware import *
|
||||||
|
|
||||||
|
__version__ = "2.2.0"
|
||||||
|
|
||||||
## Basic version of arg.parse() supporting:
|
## Basic version of arg.parse() supporting:
|
||||||
## --key=value
|
## --key=value
|
||||||
## --boolean
|
## --boolean
|
||||||
|
|
@ -28,3 +31,32 @@ for arg in sys.argv[1:]:
|
||||||
arguments[key] = val
|
arguments[key] = val
|
||||||
else:
|
else:
|
||||||
positionals.append(arg)
|
positionals.append(arg)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Learn the dark arts of argparse...
|
||||||
|
# (I summon thee dark spawn of cPython)
|
||||||
|
|
||||||
|
def run_as_a_module():
|
||||||
|
"""
|
||||||
|
Since we're running this as a 'python -m archinstall' module OR
|
||||||
|
a nuitka3 compiled version of the project.
|
||||||
|
This function and the file __main__ acts as a entry point.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Add another path for finding profiles, so that list_profiles() in Script() can find guided.py, unattended.py etc.
|
||||||
|
storage['PROFILE_PATH'].append(os.path.abspath(f'{os.path.dirname(__file__)}/examples'))
|
||||||
|
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
sys.argv.append('guided')
|
||||||
|
|
||||||
|
try:
|
||||||
|
script = Script(sys.argv[1])
|
||||||
|
except ProfileNotFound as err:
|
||||||
|
print(f"Couldn't find file: {err}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
os.chdir(os.path.abspath(os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
# Remove the example directory from the PROFILE_PATH, to avoid guided.py etc shows up in user input questions.
|
||||||
|
storage['PROFILE_PATH'].pop()
|
||||||
|
script.execute()
|
||||||
|
|
|
||||||
|
|
@ -2,33 +2,5 @@ import archinstall
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# TODO: Learn the dark arts of argparse...
|
|
||||||
# (I summon thee dark spawn of cPython)
|
|
||||||
|
|
||||||
def run_as_a_module():
|
|
||||||
"""
|
|
||||||
Since we're running this as a 'python -m archinstall' module OR
|
|
||||||
a nuitka3 compiled version of the project.
|
|
||||||
This function and the file __main__ acts as a entry point.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Add another path for finding profiles, so that list_profiles() in Script() can find guided.py, unattended.py etc.
|
|
||||||
archinstall.storage['PROFILE_PATH'].append(os.path.abspath(f'{os.path.dirname(__file__)}/examples'))
|
|
||||||
|
|
||||||
if len(sys.argv) == 1:
|
|
||||||
sys.argv.append('guided')
|
|
||||||
|
|
||||||
try:
|
|
||||||
script = archinstall.Script(sys.argv[1])
|
|
||||||
except archinstall.ProfileNotFound as err:
|
|
||||||
print(f"Couldn't find file: {err}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
os.chdir(os.path.abspath(os.path.dirname(__file__)))
|
|
||||||
|
|
||||||
# Remove the example directory from the PROFILE_PATH, to avoid guided.py etc shows up in user input questions.
|
|
||||||
archinstall.storage['PROFILE_PATH'].pop()
|
|
||||||
script.execute()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
run_as_a_module()
|
archinstall.run_as_a_module()
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,12 @@ class BlockDevice():
|
||||||
|
|
||||||
self.path = path
|
self.path = path
|
||||||
self.info = info
|
self.info = info
|
||||||
|
self.keep_partitions = True
|
||||||
self.part_cache = OrderedDict()
|
self.part_cache = OrderedDict()
|
||||||
# TODO: Currently disk encryption is a BIT missleading.
|
# TODO: Currently disk encryption is a BIT misleading.
|
||||||
# It's actually partition-encryption, but for future-proofing this
|
# It's actually partition-encryption, but for future-proofing this
|
||||||
# I'm placing the encryption password on a BlockDevice level.
|
# I'm placing the encryption password on a BlockDevice level.
|
||||||
self.encryption_passwoed = None
|
self.encryption_password = None
|
||||||
|
|
||||||
def __repr__(self, *args, **kwargs):
|
def __repr__(self, *args, **kwargs):
|
||||||
return f"BlockDevice({self.device})"
|
return f"BlockDevice({self.device})"
|
||||||
|
|
@ -126,6 +127,18 @@ class BlockDevice():
|
||||||
def partition_table_type(self):
|
def partition_table_type(self):
|
||||||
return GPT
|
return GPT
|
||||||
|
|
||||||
|
@property
|
||||||
|
def uuid(self):
|
||||||
|
log(f'BlockDevice().uuid is untested!', level=LOG_LEVELS.Warning, fg='yellow')
|
||||||
|
"""
|
||||||
|
Returns the disk UUID as returned by lsblk.
|
||||||
|
This is more reliable than relying on /dev/disk/by-partuuid as
|
||||||
|
it doesn't seam to be able to detect md raid partitions.
|
||||||
|
"""
|
||||||
|
lsblk = b''.join(sys_command(f'lsblk -J -o+UUID {self.path}'))
|
||||||
|
for partition in json.loads(lsblk.decode('UTF-8'))['blockdevices']:
|
||||||
|
return partition.get('uuid', None)
|
||||||
|
|
||||||
def has_partitions(self):
|
def has_partitions(self):
|
||||||
return len(self.partitions)
|
return len(self.partitions)
|
||||||
|
|
||||||
|
|
@ -166,7 +179,7 @@ class Partition():
|
||||||
self.mountpoint = target
|
self.mountpoint = target
|
||||||
|
|
||||||
if not self.filesystem and autodetect_filesystem:
|
if not self.filesystem and autodetect_filesystem:
|
||||||
if (fstype := mount_information.get('fstype', get_filesystem_type(self.real_device))):
|
if (fstype := mount_information.get('fstype', get_filesystem_type(path))):
|
||||||
self.filesystem = fstype
|
self.filesystem = fstype
|
||||||
|
|
||||||
if self.filesystem == 'crypto_LUKS':
|
if self.filesystem == 'crypto_LUKS':
|
||||||
|
|
@ -187,9 +200,9 @@ class Partition():
|
||||||
mount_repr = f", rel_mountpoint={self.target_mountpoint}"
|
mount_repr = f", rel_mountpoint={self.target_mountpoint}"
|
||||||
|
|
||||||
if self._encrypted:
|
if self._encrypted:
|
||||||
return f'Partition(path={self.path}, real_device={self.real_device}, fs={self.filesystem}{mount_repr})'
|
return f'Partition(path={self.path}, size={self.size}, real_device={self.real_device}, fs={self.filesystem}{mount_repr})'
|
||||||
else:
|
else:
|
||||||
return f'Partition(path={self.path}, fs={self.filesystem}{mount_repr})'
|
return f'Partition(path={self.path}, size={self.size}, fs={self.filesystem}{mount_repr})'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def uuid(self) -> str:
|
def uuid(self) -> str:
|
||||||
|
|
@ -214,14 +227,15 @@ class Partition():
|
||||||
|
|
||||||
self._encrypted = value
|
self._encrypted = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent(self):
|
||||||
|
return self.real_device
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def real_device(self):
|
def real_device(self):
|
||||||
if not self._encrypted:
|
for blockdevice in json.loads(b''.join(sys_command('lsblk -J')).decode('UTF-8'))['blockdevices']:
|
||||||
return self.path
|
if (parent := self.find_parent_of(blockdevice, os.path.basename(self.path))):
|
||||||
else:
|
return f"/dev/{parent}"
|
||||||
for blockdevice in json.loads(b''.join(sys_command(['lsblk', '-J'])).decode('UTF-8'))['blockdevices']:
|
|
||||||
if (parent := self.find_parent_of(blockdevice, os.path.basename(self.path))):
|
|
||||||
return f"/dev/{parent}"
|
|
||||||
# raise DiskError(f'Could not find appropriate parent for encrypted partition {self}')
|
# raise DiskError(f'Could not find appropriate parent for encrypted partition {self}')
|
||||||
return self.path
|
return self.path
|
||||||
|
|
||||||
|
|
@ -285,10 +299,10 @@ class Partition():
|
||||||
handle = luks2(self, None, None)
|
handle = luks2(self, None, None)
|
||||||
return handle.encrypt(self, *args, **kwargs)
|
return handle.encrypt(self, *args, **kwargs)
|
||||||
|
|
||||||
def format(self, filesystem=None, path=None, allow_formatting=None, log_formating=True):
|
def format(self, filesystem=None, path=None, allow_formatting=None, log_formatting=True):
|
||||||
"""
|
"""
|
||||||
Format can be given an overriding path, for instance /dev/null to test
|
Format can be given an overriding path, for instance /dev/null to test
|
||||||
the formating functionality and in essence the support for the given filesystem.
|
the formatting functionality and in essence the support for the given filesystem.
|
||||||
"""
|
"""
|
||||||
if filesystem is None:
|
if filesystem is None:
|
||||||
filesystem = self.filesystem
|
filesystem = self.filesystem
|
||||||
|
|
@ -306,7 +320,7 @@ class Partition():
|
||||||
if not allow_formatting:
|
if not allow_formatting:
|
||||||
raise PermissionError(f"{self} is not formatable either because instance is locked ({self.allow_formatting}) or a blocking flag was given ({allow_formatting})")
|
raise PermissionError(f"{self} is not formatable either because instance is locked ({self.allow_formatting}) or a blocking flag was given ({allow_formatting})")
|
||||||
|
|
||||||
if log_formating:
|
if log_formatting:
|
||||||
log(f'Formatting {path} -> {filesystem}', level=LOG_LEVELS.Info)
|
log(f'Formatting {path} -> {filesystem}', level=LOG_LEVELS.Info)
|
||||||
|
|
||||||
if filesystem == 'btrfs':
|
if filesystem == 'btrfs':
|
||||||
|
|
@ -366,14 +380,16 @@ class Partition():
|
||||||
if not fs:
|
if not fs:
|
||||||
if not self.filesystem: raise DiskError(f'Need to format (or define) the filesystem on {self} before mounting.')
|
if not self.filesystem: raise DiskError(f'Need to format (or define) the filesystem on {self} before mounting.')
|
||||||
fs = self.filesystem
|
fs = self.filesystem
|
||||||
## libc has some issues with loop devices, defaulting back to sys calls
|
|
||||||
# ret = libc.mount(self.path.encode(), target.encode(), fs.encode(), 0, options.encode())
|
pathlib.Path(target).mkdir(parents=True, exist_ok=True)
|
||||||
# if ret < 0:
|
|
||||||
# errno = ctypes.get_errno()
|
try:
|
||||||
# raise OSError(errno, f"Error mounting {self.path} ({fs}) on {target} with options '{options}': {os.strerror(errno)}")
|
sys_command(f'/usr/bin/mount {self.path} {target}')
|
||||||
if sys_command(f'/usr/bin/mount {self.path} {target}').exit_code == 0:
|
except SysCallError as err:
|
||||||
self.mountpoint = target
|
raise err
|
||||||
return True
|
|
||||||
|
self.mountpoint = target
|
||||||
|
return True
|
||||||
|
|
||||||
def unmount(self):
|
def unmount(self):
|
||||||
try:
|
try:
|
||||||
|
|
@ -401,7 +417,7 @@ class Partition():
|
||||||
2. UnknownFilesystemFormat that indicates that we don't support the given filesystem type
|
2. UnknownFilesystemFormat that indicates that we don't support the given filesystem type
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.format(self.filesystem, '/dev/null', log_formating=False, allow_formatting=True)
|
self.format(self.filesystem, '/dev/null', log_formatting=False, allow_formatting=True)
|
||||||
except SysCallError:
|
except SysCallError:
|
||||||
pass # We supported it, but /dev/null is not formatable as expected so the mkfs call exited with an error code
|
pass # We supported it, but /dev/null is not formatable as expected so the mkfs call exited with an error code
|
||||||
except UnknownFilesystemFormat as err:
|
except UnknownFilesystemFormat as err:
|
||||||
|
|
@ -588,6 +604,24 @@ def get_mount_info(path):
|
||||||
|
|
||||||
return output['filesystems'][0]
|
return output['filesystems'][0]
|
||||||
|
|
||||||
|
def get_partitions_in_use(mountpoint):
|
||||||
|
try:
|
||||||
|
output = b''.join(sys_command(f'/usr/bin/findmnt --json -R {mountpoint}'))
|
||||||
|
except SysCallError:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
mounts = []
|
||||||
|
|
||||||
|
output = output.decode('UTF-8')
|
||||||
|
output = json.loads(output)
|
||||||
|
for target in output.get('filesystems', []):
|
||||||
|
mounts.append(Partition(target['source'], None, filesystem=target.get('fstype', None), mountpoint=target['target']))
|
||||||
|
|
||||||
|
for child in target.get('children', []):
|
||||||
|
mounts.append(Partition(child['source'], None, filesystem=child.get('fstype', None), mountpoint=child['target']))
|
||||||
|
|
||||||
|
return mounts
|
||||||
|
|
||||||
def get_filesystem_type(path):
|
def get_filesystem_type(path):
|
||||||
try:
|
try:
|
||||||
handle = sys_command(f"blkid -o value -s TYPE {path}")
|
handle = sys_command(f"blkid -o value -s TYPE {path}")
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,5 @@ class PermissionError(BaseException):
|
||||||
pass
|
pass
|
||||||
class UserError(BaseException):
|
class UserError(BaseException):
|
||||||
pass
|
pass
|
||||||
|
class ServiceException(BaseException):
|
||||||
|
pass
|
||||||
|
|
@ -76,7 +76,7 @@ class sys_command():#Thread):
|
||||||
"""
|
"""
|
||||||
Stolen from archinstall_gui
|
Stolen from archinstall_gui
|
||||||
"""
|
"""
|
||||||
def __init__(self, cmd, callback=None, start_callback=None, peak_output=False, *args, **kwargs):
|
def __init__(self, cmd, callback=None, start_callback=None, peak_output=False, environment_vars={}, *args, **kwargs):
|
||||||
kwargs.setdefault("worker_id", gen_uid())
|
kwargs.setdefault("worker_id", gen_uid())
|
||||||
kwargs.setdefault("emulate", False)
|
kwargs.setdefault("emulate", False)
|
||||||
kwargs.setdefault("suppress_errors", False)
|
kwargs.setdefault("suppress_errors", False)
|
||||||
|
|
@ -102,6 +102,7 @@ class sys_command():#Thread):
|
||||||
self.args = args
|
self.args = args
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
self.peak_output = peak_output
|
self.peak_output = peak_output
|
||||||
|
self.environment_vars = environment_vars
|
||||||
|
|
||||||
self.kwargs.setdefault("worker", None)
|
self.kwargs.setdefault("worker", None)
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
|
|
@ -200,7 +201,7 @@ class sys_command():#Thread):
|
||||||
# Replace child process with our main process
|
# Replace child process with our main process
|
||||||
if not self.kwargs['emulate']:
|
if not self.kwargs['emulate']:
|
||||||
try:
|
try:
|
||||||
os.execv(self.cmd[0], self.cmd)
|
os.execve(self.cmd[0], self.cmd, {**os.environ, **self.environment_vars})
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
self.status = 'done'
|
self.status = 'done'
|
||||||
self.log(f"{self.cmd[0]} does not exist.", level=LOG_LEVELS.Debug)
|
self.log(f"{self.cmd[0]} does not exist.", level=LOG_LEVELS.Debug)
|
||||||
|
|
@ -304,6 +305,11 @@ class sys_command():#Thread):
|
||||||
with open(f'{self.cwd}/trace.log', 'wb') as fh:
|
with open(f'{self.cwd}/trace.log', 'wb') as fh:
|
||||||
fh.write(self.trace_log)
|
fh.write(self.trace_log)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.close(child_fd)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def prerequisite_check():
|
def prerequisite_check():
|
||||||
if not os.path.isdir("/sys/firmware/efi"):
|
if not os.path.isdir("/sys/firmware/efi"):
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,42 @@
|
||||||
import os, subprocess
|
import os, subprocess, json
|
||||||
from .general import sys_command
|
from .general import sys_command
|
||||||
from .networking import list_interfaces, enrichIfaceTypes
|
from .networking import list_interfaces, enrichIfaceTypes
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
def hasWifi():
|
AVAILABLE_GFX_DRIVERS = {
|
||||||
|
# Sub-dicts are layer-2 options to be selected
|
||||||
|
# and lists are a list of packages to be installed
|
||||||
|
'AMD / ATI' : {
|
||||||
|
'amd' : ['xf86-video-amdgpu'],
|
||||||
|
'ati' : ['xf86-video-ati']
|
||||||
|
},
|
||||||
|
'intel' : ['xf86-video-intel'],
|
||||||
|
'nvidia' : {
|
||||||
|
'open-source' : ['xf86-video-nouveau'],
|
||||||
|
'proprietary' : ['nvidia']
|
||||||
|
},
|
||||||
|
'mesa' : ['mesa'],
|
||||||
|
'fbdev' : ['xf86-video-fbdev'],
|
||||||
|
'vesa' : ['xf86-video-vesa'],
|
||||||
|
'vmware' : ['xf86-video-vmware']
|
||||||
|
}
|
||||||
|
|
||||||
|
def hasWifi()->bool:
|
||||||
return 'WIRELESS' in enrichIfaceTypes(list_interfaces().values()).values()
|
return 'WIRELESS' in enrichIfaceTypes(list_interfaces().values()).values()
|
||||||
|
|
||||||
def hasAMDCPU():
|
def hasAMDCPU()->bool:
|
||||||
if subprocess.check_output("lscpu | grep AMD", shell=True).strip().decode():
|
if subprocess.check_output("lscpu | grep AMD", shell=True).strip().decode():
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
def hasIntelCPU()->bool:
|
||||||
|
if subprocess.check_output("lscpu | grep Intel", shell=True).strip().decode():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def hasUEFI():
|
def hasUEFI()->bool:
|
||||||
return os.path.isdir('/sys/firmware/efi')
|
return os.path.isdir('/sys/firmware/efi')
|
||||||
|
|
||||||
def graphicsDevices():
|
def graphicsDevices()->dict:
|
||||||
cards = {}
|
cards = {}
|
||||||
for line in sys_command(f"lspci"):
|
for line in sys_command(f"lspci"):
|
||||||
if b' VGA ' in line:
|
if b' VGA ' in line:
|
||||||
|
|
@ -21,13 +44,28 @@ def graphicsDevices():
|
||||||
cards[identifier.strip().lower().decode('UTF-8')] = line
|
cards[identifier.strip().lower().decode('UTF-8')] = line
|
||||||
return cards
|
return cards
|
||||||
|
|
||||||
def hasNvidiaGraphics():
|
def hasNvidiaGraphics()->bool:
|
||||||
return any('nvidia' in x for x in graphicsDevices())
|
return any('nvidia' in x for x in graphicsDevices())
|
||||||
|
|
||||||
def hasAmdGraphics():
|
def hasAmdGraphics()->bool:
|
||||||
return any('amd' in x for x in graphicsDevices())
|
return any('amd' in x for x in graphicsDevices())
|
||||||
|
|
||||||
def hasIntelGraphics():
|
def hasIntelGraphics()->bool:
|
||||||
return any('intel' in x for x in graphicsDevices())
|
return any('intel' in x for x in graphicsDevices())
|
||||||
|
|
||||||
|
|
||||||
|
def cpuVendor()-> Optional[str]:
|
||||||
|
cpu_info = json.loads(subprocess.check_output("lscpu -J", shell=True).decode('utf-8'))['lscpu']
|
||||||
|
for info in cpu_info:
|
||||||
|
if info.get('field',None):
|
||||||
|
if info.get('field',None) == "Vendor ID:":
|
||||||
|
return info.get('data',None)
|
||||||
|
|
||||||
|
def isVM() -> bool:
|
||||||
|
try:
|
||||||
|
subprocess.check_call(["systemd-detect-virt"]) # systemd-detect-virt issues a none 0 exit code if it is not on a virtual machine
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
# TODO: Add more identifiers
|
# TODO: Add more identifiers
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from .mirrors import *
|
||||||
from .systemd import Networkd
|
from .systemd import Networkd
|
||||||
from .output import log, LOG_LEVELS
|
from .output import log, LOG_LEVELS
|
||||||
from .storage import storage
|
from .storage import storage
|
||||||
|
from .hardware import *
|
||||||
|
|
||||||
# Any package that the Installer() is responsible for (optional and the default ones)
|
# Any package that the Installer() is responsible for (optional and the default ones)
|
||||||
__packages__ = ["base", "base-devel", "linux", "linux-firmware", "efibootmgr", "nano", "ntp", "iwd"]
|
__packages__ = ["base", "base-devel", "linux", "linux-firmware", "efibootmgr", "nano", "ntp", "iwd"]
|
||||||
|
|
@ -38,30 +39,21 @@ class Installer():
|
||||||
:type hostname: str, optional
|
:type hostname: str, optional
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, partition, boot_partition, *, base_packages=__base_packages__, profile=None, mountpoint='/mnt', hostname='ArchInstalled', logdir=None, logfile=None):
|
def __init__(self, target, *, base_packages='base base-devel linux linux-firmware efibootmgr'):
|
||||||
self.profile = profile
|
self.target = target
|
||||||
self.hostname = hostname
|
|
||||||
self.mountpoint = mountpoint
|
|
||||||
self.init_time = time.strftime('%Y-%m-%d_%H-%M-%S')
|
self.init_time = time.strftime('%Y-%m-%d_%H-%M-%S')
|
||||||
self.milliseconds = int(str(time.time()).split('.')[1])
|
self.milliseconds = int(str(time.time()).split('.')[1])
|
||||||
|
|
||||||
if logdir:
|
|
||||||
storage['LOG_PATH'] = logdir
|
|
||||||
if logfile:
|
|
||||||
storage['LOG_FILE'] = logfile
|
|
||||||
|
|
||||||
self.helper_flags = {
|
self.helper_flags = {
|
||||||
'bootloader' : False,
|
|
||||||
'base' : False,
|
'base' : False,
|
||||||
'user' : False # Root counts as a user, if additional users are skipped.
|
'bootloader' : False
|
||||||
}
|
}
|
||||||
|
|
||||||
self.base_packages = base_packages.split(' ') if type(base_packages) is str else base_packages
|
self.base_packages = base_packages.split(' ') if type(base_packages) is str else base_packages
|
||||||
self.post_base_install = []
|
self.post_base_install = []
|
||||||
storage['session'] = self
|
|
||||||
|
|
||||||
self.partition = partition
|
storage['session'] = self
|
||||||
self.boot_partition = boot_partition
|
self.partitions = get_partitions_in_use(self.target)
|
||||||
|
|
||||||
def log(self, *args, level=LOG_LEVELS.Debug, **kwargs):
|
def log(self, *args, level=LOG_LEVELS.Debug, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -71,15 +63,10 @@ class Installer():
|
||||||
log(*args, level=level, **kwargs)
|
log(*args, level=level, **kwargs)
|
||||||
|
|
||||||
def __enter__(self, *args, **kwargs):
|
def __enter__(self, *args, **kwargs):
|
||||||
if hasUEFI():
|
|
||||||
# on bios we don't have a boot partition
|
|
||||||
self.partition.mount(self.mountpoint)
|
|
||||||
os.makedirs(f'{self.mountpoint}/boot', exist_ok=True)
|
|
||||||
self.boot_partition.mount(f'{self.mountpoint}/boot')
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *args, **kwargs):
|
def __exit__(self, *args, **kwargs):
|
||||||
# b''.join(sys_command(f'sync')) # No need to, since the underlaying fs() object will call sync.
|
# b''.join(sys_command(f'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
|
# TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager
|
||||||
|
|
||||||
if len(args) >= 2 and args[1]:
|
if len(args) >= 2 and args[1]:
|
||||||
|
|
@ -91,7 +78,7 @@ class Installer():
|
||||||
# We avoid printing /mnt/<log path> because that might confuse people if they note it down
|
# We avoid printing /mnt/<log path> because that might confuse people if they note it down
|
||||||
# and then reboot, and a identical log file will be found in the ISO medium anyway.
|
# and then reboot, and a identical log file will be found in the ISO medium anyway.
|
||||||
print(f"[!] A log file has been created here: {os.path.join(storage['LOG_PATH'], storage['LOG_FILE'])}")
|
print(f"[!] A log file has been created here: {os.path.join(storage['LOG_PATH'], storage['LOG_FILE'])}")
|
||||||
print(f" Please submit this issue (and file) to https://github.com/Torxed/archinstall/issues")
|
print(f" Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues")
|
||||||
raise args[1]
|
raise args[1]
|
||||||
|
|
||||||
self.genfstab()
|
self.genfstab()
|
||||||
|
|
@ -99,13 +86,16 @@ class Installer():
|
||||||
if not (missing_steps := self.post_install_check()):
|
if not (missing_steps := self.post_install_check()):
|
||||||
self.log('Installation completed without any errors. You may now reboot.', bg='black', fg='green', level=LOG_LEVELS.Info)
|
self.log('Installation completed without any errors. You may now reboot.', bg='black', fg='green', level=LOG_LEVELS.Info)
|
||||||
self.sync_log_to_install_medium()
|
self.sync_log_to_install_medium()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.log('Some required steps were not successfully installed/configured before leaving the installer:', bg='black', fg='red', level=LOG_LEVELS.Warning)
|
self.log('Some required steps were not successfully installed/configured before leaving the installer:', bg='black', fg='red', level=LOG_LEVELS.Warning)
|
||||||
for step in missing_steps:
|
for step in missing_steps:
|
||||||
self.log(f' - {step}', bg='black', fg='red', level=LOG_LEVELS.Warning)
|
self.log(f' - {step}', bg='black', fg='red', level=LOG_LEVELS.Warning)
|
||||||
self.log(f"Detailed error logs can be found at: {log_path}", level=LOG_LEVELS.Warning)
|
|
||||||
self.log(f"Submit this zip file as an issue to https://github.com/Torxed/archinstall/issues", level=LOG_LEVELS.Warning)
|
self.log(f"Detailed error logs can be found at: {storage['LOG_PATH']}", level=LOG_LEVELS.Warning)
|
||||||
|
self.log(f"Submit this zip file as an issue to https://github.com/archlinux/archinstall/issues", level=LOG_LEVELS.Warning)
|
||||||
|
|
||||||
self.sync_log_to_install_medium()
|
self.sync_log_to_install_medium()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -116,18 +106,18 @@ class Installer():
|
||||||
if (filename := storage.get('LOG_FILE', None)):
|
if (filename := storage.get('LOG_FILE', None)):
|
||||||
absolute_logfile = os.path.join(storage.get('LOG_PATH', './'), filename)
|
absolute_logfile = os.path.join(storage.get('LOG_PATH', './'), filename)
|
||||||
|
|
||||||
if not os.path.isdir(f"{self.mountpoint}/{os.path.dirname(absolute_logfile)}"):
|
if not os.path.isdir(f"{self.target}/{os.path.dirname(absolute_logfile)}"):
|
||||||
os.makedirs(f"{self.mountpoint}/{os.path.dirname(absolute_logfile)}")
|
os.makedirs(f"{self.target}/{os.path.dirname(absolute_logfile)}")
|
||||||
|
|
||||||
shutil.copy2(absolute_logfile, f"{self.mountpoint}/{absolute_logfile}")
|
shutil.copy2(absolute_logfile, f"{self.target}/{absolute_logfile}")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def mount(self, partition, mountpoint, create_mountpoint=True):
|
def mount(self, partition, mountpoint, create_mountpoint=True):
|
||||||
if create_mountpoint and not os.path.isdir(f'{self.mountpoint}{mountpoint}'):
|
if create_mountpoint and not os.path.isdir(f'{self.target}{mountpoint}'):
|
||||||
os.makedirs(f'{self.mountpoint}{mountpoint}')
|
os.makedirs(f'{self.target}{mountpoint}')
|
||||||
|
|
||||||
partition.mount(f'{self.mountpoint}{mountpoint}')
|
partition.mount(f'{self.target}{mountpoint}')
|
||||||
|
|
||||||
def post_install_check(self, *args, **kwargs):
|
def post_install_check(self, *args, **kwargs):
|
||||||
return [step for step, flag in self.helper_flags.items() if flag is False]
|
return [step for step, flag in self.helper_flags.items() if flag is False]
|
||||||
|
|
@ -137,7 +127,7 @@ class Installer():
|
||||||
self.log(f'Installing packages: {packages}', level=LOG_LEVELS.Info)
|
self.log(f'Installing packages: {packages}', level=LOG_LEVELS.Info)
|
||||||
|
|
||||||
if (sync_mirrors := sys_command('/usr/bin/pacman -Syy')).exit_code == 0:
|
if (sync_mirrors := sys_command('/usr/bin/pacman -Syy')).exit_code == 0:
|
||||||
if (pacstrap := sys_command(f'/usr/bin/pacstrap {self.mountpoint} {" ".join(packages)}', peak_output=True, **kwargs)).exit_code == 0:
|
if (pacstrap := sys_command(f'/usr/bin/pacstrap {self.target} {" ".join(packages)}', **kwargs)).exit_code == 0:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.log(f'Could not strap in packages: {pacstrap.exit_code}', level=LOG_LEVELS.Info)
|
self.log(f'Could not strap in packages: {pacstrap.exit_code}', level=LOG_LEVELS.Info)
|
||||||
|
|
@ -145,42 +135,41 @@ class Installer():
|
||||||
self.log(f'Could not sync mirrors: {sync_mirrors.exit_code}', level=LOG_LEVELS.Info)
|
self.log(f'Could not sync mirrors: {sync_mirrors.exit_code}', level=LOG_LEVELS.Info)
|
||||||
|
|
||||||
def set_mirrors(self, mirrors):
|
def set_mirrors(self, mirrors):
|
||||||
return use_mirrors(mirrors, destination=f'{self.mountpoint}/etc/pacman.d/mirrorlist')
|
return use_mirrors(mirrors, destination=f'{self.target}/etc/pacman.d/mirrorlist')
|
||||||
|
|
||||||
def genfstab(self, flags='-pU'):
|
def genfstab(self, flags='-pU'):
|
||||||
self.log(f"Updating {self.mountpoint}/etc/fstab", level=LOG_LEVELS.Info)
|
self.log(f"Updating {self.target}/etc/fstab", level=LOG_LEVELS.Info)
|
||||||
|
|
||||||
fstab = sys_command(f'/usr/bin/genfstab {flags} {self.mountpoint}').trace_log
|
fstab = sys_command(f'/usr/bin/genfstab {flags} {self.target}').trace_log
|
||||||
with open(f"{self.mountpoint}/etc/fstab", 'ab') as fstab_fh:
|
with open(f"{self.target}/etc/fstab", 'ab') as fstab_fh:
|
||||||
fstab_fh.write(fstab)
|
fstab_fh.write(fstab)
|
||||||
|
|
||||||
if not os.path.isfile(f'{self.mountpoint}/etc/fstab'):
|
if not os.path.isfile(f'{self.target}/etc/fstab'):
|
||||||
raise RequirementError(f'Could not generate fstab, strapping in packages most likely failed (disk out of space?)\n{o}')
|
raise RequirementError(f'Could not generate fstab, strapping in packages most likely failed (disk out of space?)\n{fstab}')
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_hostname(self, hostname=None, *args, **kwargs):
|
def set_hostname(self, hostname :str, *args, **kwargs):
|
||||||
if not hostname: hostname = self.hostname
|
with open(f'{self.target}/etc/hostname', 'w') as fh:
|
||||||
with open(f'{self.mountpoint}/etc/hostname', 'w') as fh:
|
fh.write(hostname + '\n')
|
||||||
fh.write(self.hostname + '\n')
|
|
||||||
|
|
||||||
def set_locale(self, locale, encoding='UTF-8', *args, **kwargs):
|
def set_locale(self, locale, encoding='UTF-8', *args, **kwargs):
|
||||||
if not len(locale): return True
|
if not len(locale): return True
|
||||||
|
|
||||||
with open(f'{self.mountpoint}/etc/locale.gen', 'a') as fh:
|
with open(f'{self.target}/etc/locale.gen', 'a') as fh:
|
||||||
fh.write(f'{locale}.{encoding} {encoding}\n')
|
fh.write(f'{locale}.{encoding} {encoding}\n')
|
||||||
with open(f'{self.mountpoint}/etc/locale.conf', 'w') as fh:
|
with open(f'{self.target}/etc/locale.conf', 'w') as fh:
|
||||||
fh.write(f'LANG={locale}.{encoding}\n')
|
fh.write(f'LANG={locale}.{encoding}\n')
|
||||||
|
|
||||||
return True if sys_command(f'/usr/bin/arch-chroot {self.mountpoint} locale-gen').exit_code == 0 else False
|
return True if sys_command(f'/usr/bin/arch-chroot {self.target} locale-gen').exit_code == 0 else False
|
||||||
|
|
||||||
def set_timezone(self, zone, *args, **kwargs):
|
def set_timezone(self, zone, *args, **kwargs):
|
||||||
if not zone: return True
|
if not zone: return True
|
||||||
if not len(zone): return True # Redundant
|
if not len(zone): return True # Redundant
|
||||||
|
|
||||||
if (pathlib.Path("/usr")/"share"/"zoneinfo"/zone).exists():
|
if (pathlib.Path("/usr")/"share"/"zoneinfo"/zone).exists():
|
||||||
(pathlib.Path(self.mountpoint)/"etc"/"localtime").unlink(missing_ok=True)
|
(pathlib.Path(self.target)/"etc"/"localtime").unlink(missing_ok=True)
|
||||||
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} ln -s /usr/share/zoneinfo/{zone} /etc/localtime')
|
sys_command(f'/usr/bin/arch-chroot {self.target} ln -s /usr/share/zoneinfo/{zone} /etc/localtime')
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.log(
|
self.log(
|
||||||
|
|
@ -195,16 +184,21 @@ class Installer():
|
||||||
if self.enable_service('ntpd'):
|
if self.enable_service('ntpd'):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def enable_service(self, service):
|
def enable_service(self, *services):
|
||||||
self.log(f'Enabling service {service}', level=LOG_LEVELS.Info)
|
for service in services:
|
||||||
return self.arch_chroot(f'systemctl enable {service}').exit_code == 0
|
self.log(f'Enabling service {service}', level=LOG_LEVELS.Info)
|
||||||
|
if (output := self.arch_chroot(f'systemctl enable {service}')).exit_code != 0:
|
||||||
|
raise ServiceException(f"Unable to start service {service}: {output}")
|
||||||
|
|
||||||
def run_command(self, cmd, *args, **kwargs):
|
def run_command(self, cmd, *args, **kwargs):
|
||||||
return sys_command(f'/usr/bin/arch-chroot {self.mountpoint} {cmd}')
|
return sys_command(f'/usr/bin/arch-chroot {self.target} {cmd}')
|
||||||
|
|
||||||
def arch_chroot(self, cmd, *args, **kwargs):
|
def arch_chroot(self, cmd, *args, **kwargs):
|
||||||
return self.run_command(cmd)
|
return self.run_command(cmd)
|
||||||
|
|
||||||
|
def drop_to_shell(self):
|
||||||
|
subprocess.check_call(f"/usr/bin/arch-chroot {self.target}", shell=True)
|
||||||
|
|
||||||
def configure_nic(self, nic, dhcp=True, ip=None, gateway=None, dns=None, *args, **kwargs):
|
def configure_nic(self, nic, dhcp=True, ip=None, gateway=None, dns=None, *args, **kwargs):
|
||||||
if dhcp:
|
if dhcp:
|
||||||
conf = Networkd(Match={"Name": nic}, Network={"DHCP": "yes"})
|
conf = Networkd(Match={"Name": nic}, Network={"DHCP": "yes"})
|
||||||
|
|
@ -220,15 +214,15 @@ class Installer():
|
||||||
|
|
||||||
conf = Networkd(Match={"Name": nic}, Network=network)
|
conf = Networkd(Match={"Name": nic}, Network=network)
|
||||||
|
|
||||||
with open(f"{self.mountpoint}/etc/systemd/network/10-{nic}.network", "a") as netconf:
|
with open(f"{self.target}/etc/systemd/network/10-{nic}.network", "a") as netconf:
|
||||||
netconf.write(str(conf))
|
netconf.write(str(conf))
|
||||||
|
|
||||||
def copy_ISO_network_config(self, enable_services=False):
|
def copy_ISO_network_config(self, enable_services=False):
|
||||||
# Copy (if any) iwd password and config files
|
# Copy (if any) iwd password and config files
|
||||||
if os.path.isdir('/var/lib/iwd/'):
|
if os.path.isdir('/var/lib/iwd/'):
|
||||||
if (psk_files := glob.glob('/var/lib/iwd/*.psk')):
|
if (psk_files := glob.glob('/var/lib/iwd/*.psk')):
|
||||||
if not os.path.isdir(f"{self.mountpoint}/var/lib/iwd"):
|
if not os.path.isdir(f"{self.target}/var/lib/iwd"):
|
||||||
os.makedirs(f"{self.mountpoint}/var/lib/iwd")
|
os.makedirs(f"{self.target}/var/lib/iwd")
|
||||||
|
|
||||||
if enable_services:
|
if enable_services:
|
||||||
# If we haven't installed the base yet (function called pre-maturely)
|
# If we haven't installed the base yet (function called pre-maturely)
|
||||||
|
|
@ -248,81 +242,103 @@ class Installer():
|
||||||
self.enable_service('iwd')
|
self.enable_service('iwd')
|
||||||
|
|
||||||
for psk in psk_files:
|
for psk in psk_files:
|
||||||
shutil.copy2(psk, f"{self.mountpoint}/var/lib/iwd/{os.path.basename(psk)}")
|
shutil.copy2(psk, f"{self.target}/var/lib/iwd/{os.path.basename(psk)}")
|
||||||
|
|
||||||
# Copy (if any) systemd-networkd config files
|
# Copy (if any) systemd-networkd config files
|
||||||
if (netconfigurations := glob.glob('/etc/systemd/network/*')):
|
if (netconfigurations := glob.glob('/etc/systemd/network/*')):
|
||||||
if not os.path.isdir(f"{self.mountpoint}/etc/systemd/network/"):
|
if not os.path.isdir(f"{self.target}/etc/systemd/network/"):
|
||||||
os.makedirs(f"{self.mountpoint}/etc/systemd/network/")
|
os.makedirs(f"{self.target}/etc/systemd/network/")
|
||||||
|
|
||||||
for netconf_file in netconfigurations:
|
for netconf_file in netconfigurations:
|
||||||
shutil.copy2(netconf_file, f"{self.mountpoint}/etc/systemd/network/{os.path.basename(netconf_file)}")
|
shutil.copy2(netconf_file, f"{self.target}/etc/systemd/network/{os.path.basename(netconf_file)}")
|
||||||
|
|
||||||
if enable_services:
|
if enable_services:
|
||||||
# If we haven't installed the base yet (function called pre-maturely)
|
# If we haven't installed the base yet (function called pre-maturely)
|
||||||
if self.helper_flags.get('base', False) is False:
|
if self.helper_flags.get('base', False) is False:
|
||||||
def post_install_enable_networkd_resolved(*args, **kwargs):
|
def post_install_enable_networkd_resolved(*args, **kwargs):
|
||||||
self.enable_service('systemd-networkd')
|
self.enable_service('systemd-networkd', 'systemd-resolved')
|
||||||
self.enable_service('systemd-resolved')
|
|
||||||
|
|
||||||
self.post_base_install.append(post_install_enable_networkd_resolved)
|
self.post_base_install.append(post_install_enable_networkd_resolved)
|
||||||
# Otherwise, we can go ahead and enable the services
|
# Otherwise, we can go ahead and enable the services
|
||||||
else:
|
else:
|
||||||
self.enable_service('systemd-networkd')
|
self.enable_service('systemd-networkd', 'systemd-resolved')
|
||||||
self.enable_service('systemd-resolved')
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def detect_encryption(self, partition):
|
||||||
|
if partition.encrypted:
|
||||||
|
return partition
|
||||||
|
elif partition.parent not in partition.path and Partition(partition.parent, None, autodetect_filesystem=True).filesystem == 'crypto_LUKS':
|
||||||
|
return Partition(partition.parent, None, autodetect_filesystem=True)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def minimal_installation(self):
|
def minimal_installation(self):
|
||||||
## Add nessecary packages if encrypting the drive
|
## Add necessary packages if encrypting the drive
|
||||||
## (encrypted partitions default to btrfs for now, so we need btrfs-progs)
|
## (encrypted partitions default to btrfs for now, so we need btrfs-progs)
|
||||||
## TODO: Perhaps this should be living in the function which dictates
|
## TODO: Perhaps this should be living in the function which dictates
|
||||||
## the partitioning. Leaving here for now.
|
## the partitioning. Leaving here for now.
|
||||||
if self.partition.filesystem == 'btrfs':
|
|
||||||
#if self.partition.encrypted:
|
MODULES = []
|
||||||
self.base_packages.append('btrfs-progs')
|
BINARIES = []
|
||||||
if self.partition.filesystem == 'xfs':
|
FILES = []
|
||||||
self.base_packages.append('xfsprogs')
|
HOOKS = ["base", "udev", "autodetect", "keyboard", "keymap", "modconf", "block", "filesystems", "fsck"]
|
||||||
if self.partition.filesystem == 'f2fs':
|
|
||||||
self.base_packages.append('f2fs-tools')
|
for partition in self.partitions:
|
||||||
if not(hasUEFI()):
|
if partition.filesystem == 'btrfs':
|
||||||
|
#if partition.encrypted:
|
||||||
|
self.base_packages.append('btrfs-progs')
|
||||||
|
if partition.filesystem == 'xfs':
|
||||||
|
self.base_packages.append('xfsprogs')
|
||||||
|
if partition.filesystem == 'f2fs':
|
||||||
|
self.base_packages.append('f2fs-tools')
|
||||||
|
|
||||||
|
# Configure mkinitcpio to handle some specific use cases.
|
||||||
|
if partition.filesystem == 'btrfs':
|
||||||
|
if 'btrfs' not in MODULES:
|
||||||
|
MODULES.append('btrfs')
|
||||||
|
if '/usr/bin/btrfs-progs' not in BINARIES:
|
||||||
|
BINARIES.append('/usr/bin/btrfs')
|
||||||
|
|
||||||
|
if self.detect_encryption(partition):
|
||||||
|
if 'encrypt' not in HOOKS:
|
||||||
|
HOOKS.insert(HOOKS.index('filesystems'), 'encrypt')
|
||||||
|
|
||||||
|
if not(hasUEFI()): # TODO: Allow for grub even on EFI
|
||||||
self.base_packages.append('grub')
|
self.base_packages.append('grub')
|
||||||
|
|
||||||
self.pacstrap(self.base_packages)
|
self.pacstrap(self.base_packages)
|
||||||
self.helper_flags['base-strapped'] = True
|
self.helper_flags['base-strapped'] = True
|
||||||
#self.genfstab()
|
#self.genfstab()
|
||||||
|
if not isVM():
|
||||||
with open(f"{self.mountpoint}/etc/fstab", "a") as fstab:
|
vendor = cpuVendor()
|
||||||
|
if vendor == "AuthenticAMD":
|
||||||
|
self.base_packages.append("amd-ucode")
|
||||||
|
elif vendor == "GenuineIntel":
|
||||||
|
self.base_packages.append("intel-ucode")
|
||||||
|
else:
|
||||||
|
self.log("Unknown cpu vendor not installing ucode")
|
||||||
|
with open(f"{self.target}/etc/fstab", "a") as fstab:
|
||||||
fstab.write(
|
fstab.write(
|
||||||
"\ntmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0\n"
|
"\ntmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0\n"
|
||||||
) # Redundant \n at the start? who knows?
|
) # Redundant \n at the start? who knows?
|
||||||
|
|
||||||
## TODO: Support locale and timezone
|
## TODO: Support locale and timezone
|
||||||
#os.remove(f'{self.mountpoint}/etc/localtime')
|
#os.remove(f'{self.target}/etc/localtime')
|
||||||
#sys_command(f'/usr/bin/arch-chroot {self.mountpoint} ln -s /usr/share/zoneinfo/{localtime} /etc/localtime')
|
#sys_command(f'/usr/bin/arch-chroot {self.target} ln -s /usr/share/zoneinfo/{localtime} /etc/localtime')
|
||||||
#sys_command('/usr/bin/arch-chroot /mnt hwclock --hctosys --localtime')
|
#sys_command('/usr/bin/arch-chroot /mnt hwclock --hctosys --localtime')
|
||||||
self.set_hostname()
|
self.set_hostname('archinstall')
|
||||||
self.set_locale('en_US')
|
self.set_locale('en_US')
|
||||||
|
|
||||||
# TODO: Use python functions for this
|
# TODO: Use python functions for this
|
||||||
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} chmod 700 /root')
|
sys_command(f'/usr/bin/arch-chroot {self.target} chmod 700 /root')
|
||||||
|
|
||||||
# Configure mkinitcpio to handle some specific use cases.
|
with open(f'{self.target}/etc/mkinitcpio.conf', 'w') as mkinit:
|
||||||
# TODO: Yes, we should not overwrite the entire thing, but for now this should be fine
|
mkinit.write(f"MODULES=({' '.join(MODULES)})\n")
|
||||||
# since we just installed the base system.
|
mkinit.write(f"BINARIES=({' '.join(BINARIES)})\n")
|
||||||
if self.partition.filesystem == 'btrfs':
|
mkinit.write(f"FILES=({' '.join(FILES)})\n")
|
||||||
with open(f'{self.mountpoint}/etc/mkinitcpio.conf', 'w') as mkinit:
|
mkinit.write(f"HOOKS=({' '.join(HOOKS)})\n")
|
||||||
mkinit.write('MODULES=(btrfs)\n')
|
sys_command(f'/usr/bin/arch-chroot {self.target} mkinitcpio -p linux')
|
||||||
mkinit.write('BINARIES=(/usr/bin/btrfs)\n')
|
|
||||||
mkinit.write('FILES=()\n')
|
|
||||||
mkinit.write('HOOKS=(base udev autodetect modconf block encrypt filesystems keymap keyboard fsck)\n')
|
|
||||||
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} mkinitcpio -p linux')
|
|
||||||
elif self.partition.encrypted:
|
|
||||||
with open(f'{self.mountpoint}/etc/mkinitcpio.conf', 'w') as mkinit:
|
|
||||||
mkinit.write('MODULES=()\n')
|
|
||||||
mkinit.write('BINARIES=()\n')
|
|
||||||
mkinit.write('FILES=()\n')
|
|
||||||
mkinit.write('HOOKS=(base udev autodetect modconf block encrypt filesystems keymap keyboard fsck)\n')
|
|
||||||
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} mkinitcpio -p linux')
|
|
||||||
|
|
||||||
self.helper_flags['base'] = True
|
self.helper_flags['base'] = True
|
||||||
|
|
||||||
|
|
@ -334,7 +350,15 @@ class Installer():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def add_bootloader(self, bootloader='systemd-bootctl'):
|
def add_bootloader(self, bootloader='systemd-bootctl'):
|
||||||
self.log(f'Adding bootloader {bootloader} to {self.boot_partition}', level=LOG_LEVELS.Info)
|
boot_partition = None
|
||||||
|
root_partition = None
|
||||||
|
for partition in self.partitions:
|
||||||
|
if partition.mountpoint == self.target+'/boot':
|
||||||
|
boot_partition = partition
|
||||||
|
elif partition.mountpoint == self.target:
|
||||||
|
root_partition = partition
|
||||||
|
|
||||||
|
self.log(f'Adding bootloader {bootloader} to {boot_partition}', level=LOG_LEVELS.Info)
|
||||||
|
|
||||||
if bootloader == 'systemd-bootctl':
|
if bootloader == 'systemd-bootctl':
|
||||||
if not hasUEFI():
|
if not hasUEFI():
|
||||||
|
|
@ -344,11 +368,11 @@ class Installer():
|
||||||
# And in which case we should do some clean up.
|
# And in which case we should do some clean up.
|
||||||
|
|
||||||
# Install the boot loader
|
# Install the boot loader
|
||||||
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} bootctl --no-variables --path=/boot install')
|
sys_command(f'/usr/bin/arch-chroot {self.target} bootctl --no-variables --path=/boot install')
|
||||||
|
|
||||||
# Modify or create a loader.conf
|
# Modify or create a loader.conf
|
||||||
if os.path.isfile(f'{self.mountpoint}/boot/loader/loader.conf'):
|
if os.path.isfile(f'{self.target}/boot/loader/loader.conf'):
|
||||||
with open(f'{self.mountpoint}/boot/loader/loader.conf', 'r') as loader:
|
with open(f'{self.target}/boot/loader/loader.conf', 'r') as loader:
|
||||||
loader_data = loader.read().split('\n')
|
loader_data = loader.read().split('\n')
|
||||||
else:
|
else:
|
||||||
loader_data = [
|
loader_data = [
|
||||||
|
|
@ -356,7 +380,7 @@ class Installer():
|
||||||
f"timeout 5"
|
f"timeout 5"
|
||||||
]
|
]
|
||||||
|
|
||||||
with open(f'{self.mountpoint}/boot/loader/loader.conf', 'w') as loader:
|
with open(f'{self.target}/boot/loader/loader.conf', 'w') as loader:
|
||||||
for line in loader_data:
|
for line in loader_data:
|
||||||
if line[:8] == 'default ':
|
if line[:8] == 'default ':
|
||||||
loader.write(f'default {self.init_time}\n')
|
loader.write(f'default {self.init_time}\n')
|
||||||
|
|
@ -366,49 +390,47 @@ class Installer():
|
||||||
## For some reason, blkid and /dev/disk/by-uuid are not getting along well.
|
## For some reason, blkid and /dev/disk/by-uuid are not getting along well.
|
||||||
## And blkid is wrong in terms of LUKS.
|
## And blkid is wrong in terms of LUKS.
|
||||||
#UUID = sys_command('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip()
|
#UUID = sys_command('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip()
|
||||||
|
|
||||||
# Setup the loader entry
|
# Setup the loader entry
|
||||||
with open(f'{self.mountpoint}/boot/loader/entries/{self.init_time}.conf', 'w') as entry:
|
with open(f'{self.target}/boot/loader/entries/{self.init_time}.conf', 'w') as entry:
|
||||||
entry.write(f'# Created by: archinstall\n')
|
entry.write(f'# Created by: archinstall\n')
|
||||||
entry.write(f'# Created on: {self.init_time}\n')
|
entry.write(f'# Created on: {self.init_time}\n')
|
||||||
entry.write(f'title Arch Linux\n')
|
entry.write(f'title Arch Linux\n')
|
||||||
entry.write(f'linux /vmlinuz-linux\n')
|
entry.write(f'linux /vmlinuz-linux\n')
|
||||||
|
if not isVM():
|
||||||
|
vendor = cpuVendor()
|
||||||
|
if vendor == "AuthenticAMD":
|
||||||
|
entry.write("initrd /amd-ucode.img\n")
|
||||||
|
elif vendor == "GenuineIntel":
|
||||||
|
entry.write("initrd /intel-ucode.img\n")
|
||||||
|
else:
|
||||||
|
self.log("unknow cpu vendor, not adding ucode to systemd-boot config")
|
||||||
entry.write(f'initrd /initramfs-linux.img\n')
|
entry.write(f'initrd /initramfs-linux.img\n')
|
||||||
## blkid doesn't trigger on loopback devices really well,
|
## blkid doesn't trigger on loopback devices really well,
|
||||||
## so we'll use the old manual method until we get that sorted out.
|
## so we'll use the old manual method until we get that sorted out.
|
||||||
|
|
||||||
|
|
||||||
if self.partition.encrypted:
|
if (real_device := self.detect_encryption(root_partition)):
|
||||||
log(f"Identifying root partition by DISK-UUID on {self.partition}, looking for '{os.path.basename(self.partition.real_device)}'.", level=LOG_LEVELS.Debug)
|
# TODO: We need to detect if the encrypted device is a whole disk encryption,
|
||||||
for root, folders, uids in os.walk('/dev/disk/by-uuid'):
|
# or simply a partition encryption. Right now we assume it's a partition (and we always have)
|
||||||
for uid in uids:
|
log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.uuid}'.", level=LOG_LEVELS.Debug)
|
||||||
real_path = os.path.realpath(os.path.join(root, uid))
|
entry.write(f'options cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n')
|
||||||
|
|
||||||
log(f"Checking root partition match {os.path.basename(real_path)} against {os.path.basename(self.partition.real_device)}: {os.path.basename(real_path) == os.path.basename(self.partition.real_device)}", level=LOG_LEVELS.Debug)
|
|
||||||
if not os.path.basename(real_path) == os.path.basename(self.partition.real_device): continue
|
|
||||||
|
|
||||||
entry.write(f'options cryptdevice=UUID={uid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n')
|
|
||||||
|
|
||||||
self.helper_flags['bootloader'] = bootloader
|
|
||||||
return True
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
log(f"Identifying root partition by PART-UUID on {self.partition}, looking for '{os.path.basename(self.partition.path)}'.", level=LOG_LEVELS.Debug)
|
log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.uuid}'.", level=LOG_LEVELS.Debug)
|
||||||
entry.write(f'options root=PARTUUID={self.partition.uuid} rw intel_pstate=no_hwp\n')
|
entry.write(f'options root=PARTUUID={root_partition.uuid} rw intel_pstate=no_hwp\n')
|
||||||
|
|
||||||
self.helper_flags['bootloader'] = bootloader
|
self.helper_flags['bootloader'] = bootloader
|
||||||
return True
|
return True
|
||||||
|
|
||||||
raise RequirementError(f"Could not identify the UUID of {self.partition}, there for {self.mountpoint}/boot/loader/entries/arch.conf will be broken until fixed.")
|
raise RequirementError(f"Could not identify the UUID of {self.partition}, there for {self.target}/boot/loader/entries/arch.conf will be broken until fixed.")
|
||||||
elif bootloader == "grub-install":
|
elif bootloader == "grub-install":
|
||||||
if hasUEFI():
|
if hasUEFI():
|
||||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB'))
|
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB'))
|
||||||
sys_command('/usr/bin/arch-chroot grub-mkconfig -o /boot/grub/grub.cfg')
|
sys_command('/usr/bin/arch-chroot grub-mkconfig -o /boot/grub/grub.cfg')
|
||||||
else:
|
else:
|
||||||
root_device = subprocess.check_output(f'basename "$(readlink -f "/sys/class/block/{self.partition.path.strip("/dev/")}/..")',shell=True).decode().strip()
|
root_device = subprocess.check_output(f'basename "$(readlink -f "/sys/class/block/{root_partition.path.strip("/dev/")}/..")', shell=True).decode().strip()
|
||||||
if root_device == "block":
|
if root_device == "block":
|
||||||
root_device = f"{self.partition.path}"
|
root_device = f"{root_partition.path}"
|
||||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} grub-install --target=--target=i386-pc /dev/{root_device}'))
|
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} grub-install --target=--target=i386-pc /dev/{root_device}'))
|
||||||
sys_command('/usr/bin/arch-chroot grub-mkconfig -o /boot/grub/grub.cfg')
|
sys_command('/usr/bin/arch-chroot grub-mkconfig -o /boot/grub/grub.cfg')
|
||||||
else:
|
else:
|
||||||
raise RequirementError(f"Unknown (or not yet implemented) bootloader added to add_bootloader(): {bootloader}")
|
raise RequirementError(f"Unknown (or not yet implemented) bootloader added to add_bootloader(): {bootloader}")
|
||||||
|
|
@ -421,7 +443,7 @@ class Installer():
|
||||||
# The tricky thing with doing the import archinstall.session instead is that
|
# The tricky thing with doing the import archinstall.session instead is that
|
||||||
# profiles might be run from a different chroot, and there's no way we can
|
# profiles might be run from a different chroot, and there's no way we can
|
||||||
# guarantee file-path safety when accessing the installer object that way.
|
# guarantee file-path safety when accessing the installer object that way.
|
||||||
# Doing the __builtins__ replacement, ensures that the global vriable "installation"
|
# Doing the __builtins__ replacement, ensures that the global variable "installation"
|
||||||
# is always kept up to date. It's considered a nasty hack - but it's a safe way
|
# is always kept up to date. It's considered a nasty hack - but it's a safe way
|
||||||
# of ensuring 100% accuracy of archinstall session variables.
|
# of ensuring 100% accuracy of archinstall session variables.
|
||||||
__builtins__['installation'] = self
|
__builtins__['installation'] = self
|
||||||
|
|
@ -434,19 +456,19 @@ class Installer():
|
||||||
|
|
||||||
def enable_sudo(self, entity :str, group=False):
|
def enable_sudo(self, entity :str, group=False):
|
||||||
self.log(f'Enabling sudo permissions for {entity}.', level=LOG_LEVELS.Info)
|
self.log(f'Enabling sudo permissions for {entity}.', level=LOG_LEVELS.Info)
|
||||||
with open(f'{self.mountpoint}/etc/sudoers', 'a') as sudoers:
|
with open(f'{self.target}/etc/sudoers', 'a') as sudoers:
|
||||||
sudoers.write(f'{"%" if group else ""}{entity} ALL=(ALL) ALL\n')
|
sudoers.write(f'{"%" if group else ""}{entity} ALL=(ALL) ALL\n')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def user_create(self, user :str, password=None, groups=[], sudo=False):
|
def user_create(self, user :str, password=None, groups=[], sudo=False):
|
||||||
self.log(f'Creating user {user}', level=LOG_LEVELS.Info)
|
self.log(f'Creating user {user}', level=LOG_LEVELS.Info)
|
||||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} useradd -m -G wheel {user}'))
|
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} useradd -m -G wheel {user}'))
|
||||||
if password:
|
if password:
|
||||||
self.user_set_pw(user, password)
|
self.user_set_pw(user, password)
|
||||||
|
|
||||||
if groups:
|
if groups:
|
||||||
for group in groups:
|
for group in groups:
|
||||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} gpasswd -a {user} {group}'))
|
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} gpasswd -a {user} {group}'))
|
||||||
|
|
||||||
if sudo and self.enable_sudo(user):
|
if sudo and self.enable_sudo(user):
|
||||||
self.helper_flags['user'] = True
|
self.helper_flags['user'] = True
|
||||||
|
|
@ -458,12 +480,20 @@ class Installer():
|
||||||
# This means the root account isn't locked/disabled with * in /etc/passwd
|
# This means the root account isn't locked/disabled with * in /etc/passwd
|
||||||
self.helper_flags['user'] = True
|
self.helper_flags['user'] = True
|
||||||
|
|
||||||
o = b''.join(sys_command(f"/usr/bin/arch-chroot {self.mountpoint} sh -c \"echo '{user}:{password}' | chpasswd\""))
|
o = b''.join(sys_command(f"/usr/bin/arch-chroot {self.target} sh -c \"echo '{user}:{password}' | chpasswd\""))
|
||||||
|
pass
|
||||||
|
|
||||||
|
def user_set_shell(self, user, shell):
|
||||||
|
self.log(f'Setting shell for {user} to {shell}', level=LOG_LEVELS.Info)
|
||||||
|
|
||||||
|
o = b''.join(sys_command(f"/usr/bin/arch-chroot {self.target} sh -c \"chsh -s {shell} {user}\""))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def set_keyboard_language(self, language):
|
def set_keyboard_language(self, language):
|
||||||
if len(language.strip()):
|
if len(language.strip()):
|
||||||
with open(f'{self.mountpoint}/etc/vconsole.conf', 'w') as vconsole:
|
with open(f'{self.target}/etc/vconsole.conf', 'w') as vconsole:
|
||||||
vconsole.write(f'KEYMAP={language}\n')
|
vconsole.write(f'KEYMAP={language}\n')
|
||||||
vconsole.write(f'FONT=lat9w-16\n')
|
vconsole.write(f'FONT=lat9w-16\n')
|
||||||
|
else:
|
||||||
|
self.log(f'Keyboard language was not changed from default (no language specified).', fg="yellow", level=LOG_LEVELS.Info)
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,25 @@
|
||||||
|
import subprocess
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
# from .general import sys_command
|
# from .general import sys_command
|
||||||
|
|
||||||
def list_keyboard_languages(layout='qwerty'):
|
def list_keyboard_languages():
|
||||||
locale_dir = '/usr/share/kbd/keymaps/'
|
locale_dir = '/usr/share/kbd/keymaps/'
|
||||||
|
|
||||||
if not os.path.isdir(locale_dir):
|
if not os.path.isdir(locale_dir):
|
||||||
raise RequirementError(f'Directory containing locales does not exist: {locale_dir}')
|
raise RequirementError(f'Directory containing locales does not exist: {locale_dir}')
|
||||||
|
|
||||||
for root, folders, files in os.walk(locale_dir):
|
for root, folders, files in os.walk(locale_dir):
|
||||||
# Since qwerty is under /i386/ but other layouts are
|
|
||||||
# in different spots, we'll need to filter the last foldername
|
|
||||||
# of the path to verify against the desired layout.
|
|
||||||
if os.path.basename(root) != layout:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
if os.path.splitext(file)[1] == '.gz':
|
if os.path.splitext(file)[1] == '.gz':
|
||||||
yield file.strip('.gz').strip('.map')
|
yield file.strip('.gz').strip('.map')
|
||||||
|
|
||||||
def search_keyboard_layout(filter, layout='qwerty'):
|
def search_keyboard_layout(filter):
|
||||||
for language in list_keyboard_languages(layout):
|
for language in list_keyboard_languages():
|
||||||
if filter.lower() in language.lower():
|
if filter.lower() in language.lower():
|
||||||
yield language
|
yield language
|
||||||
|
|
||||||
def set_keyboard_language(locale):
|
def set_keyboard_language(locale):
|
||||||
return os.system(f'loadkeys {locale}') == 0
|
return subprocess.call(['loadkeys', locale]) == 0
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
import time
|
||||||
|
import pathlib
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .general import *
|
from .general import *
|
||||||
from .disk import Partition
|
from .disk import Partition
|
||||||
|
|
@ -43,8 +45,6 @@ class luks2():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def encrypt(self, partition, password=None, key_size=512, hash_type='sha512', iter_time=10000, key_file=None):
|
def encrypt(self, partition, password=None, key_size=512, hash_type='sha512', iter_time=10000, key_file=None):
|
||||||
# TODO: We should be able to integrate this into the main log some how.
|
|
||||||
# Perhaps post-mortem?
|
|
||||||
if not self.partition.allow_formatting:
|
if not self.partition.allow_formatting:
|
||||||
raise DiskError(f'Could not encrypt volume {self.partition} due to it having a formatting lock.')
|
raise DiskError(f'Could not encrypt volume {self.partition} due to it having a formatting lock.')
|
||||||
|
|
||||||
|
|
@ -116,7 +116,7 @@ class luks2():
|
||||||
|
|
||||||
def unlock(self, partition, mountpoint, key_file):
|
def unlock(self, partition, mountpoint, key_file):
|
||||||
"""
|
"""
|
||||||
Mounts a lukts2 compatible partition to a certain mountpoint.
|
Mounts a luks2 compatible partition to a certain mountpoint.
|
||||||
Keyfile must be specified as there's no way to interact with the pw-prompt atm.
|
Keyfile must be specified as there's no way to interact with the pw-prompt atm.
|
||||||
|
|
||||||
:param mountpoint: The name without absolute path, for instance "luksdev" will point to /dev/mapper/luksdev
|
:param mountpoint: The name without absolute path, for instance "luksdev" will point to /dev/mapper/luksdev
|
||||||
|
|
@ -125,6 +125,11 @@ class luks2():
|
||||||
from .disk import get_filesystem_type
|
from .disk import get_filesystem_type
|
||||||
if '/' in mountpoint:
|
if '/' in mountpoint:
|
||||||
os.path.basename(mountpoint) # TODO: Raise exception instead?
|
os.path.basename(mountpoint) # TODO: Raise exception instead?
|
||||||
|
|
||||||
|
wait_timer = time.time()
|
||||||
|
while pathlib.Path(partition.path).exists() is False and time.time() - wait_timer < 10:
|
||||||
|
time.sleep(0.025)
|
||||||
|
|
||||||
sys_command(f'/usr/bin/cryptsetup open {partition.path} {mountpoint} --key-file {os.path.abspath(key_file)} --type luks2')
|
sys_command(f'/usr/bin/cryptsetup open {partition.path} {mountpoint} --key-file {os.path.abspath(key_file)} --type luks2')
|
||||||
if os.path.islink(f'/dev/mapper/{mountpoint}'):
|
if os.path.islink(f'/dev/mapper/{mountpoint}'):
|
||||||
self.mapdev = f'/dev/mapper/{mountpoint}'
|
self.mapdev = f'/dev/mapper/{mountpoint}'
|
||||||
|
|
|
||||||
|
|
@ -74,10 +74,15 @@ def re_rank_mirrors(top=10, *positionals, **kwargs):
|
||||||
|
|
||||||
def list_mirrors():
|
def list_mirrors():
|
||||||
url = f"https://archlinux.org/mirrorlist/?protocol=https&ip_version=4&ip_version=6&use_mirror_status=on"
|
url = f"https://archlinux.org/mirrorlist/?protocol=https&ip_version=4&ip_version=6&use_mirror_status=on"
|
||||||
|
|
||||||
response = urllib.request.urlopen(url)
|
|
||||||
regions = {}
|
regions = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = urllib.request.urlopen(url)
|
||||||
|
except urllib.error.URLError as err:
|
||||||
|
log(f'Could not fetch an active mirror-list: {err}', level=LOG_LEVELS.Warning, fg="yellow")
|
||||||
|
return regions
|
||||||
|
|
||||||
|
|
||||||
region = 'Unknown region'
|
region = 'Unknown region'
|
||||||
for line in response.readlines():
|
for line in response.readlines():
|
||||||
if len(line.strip()) == 0:
|
if len(line.strip()) == 0:
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ def wirelessScan(interface):
|
||||||
|
|
||||||
storage['_WIFI'][interface]['scanning'] = True
|
storage['_WIFI'][interface]['scanning'] = True
|
||||||
|
|
||||||
# TOOD: Full WiFi experience might get evolved in the future, pausing for now 2021-01-25
|
# TODO: Full WiFi experience might get evolved in the future, pausing for now 2021-01-25
|
||||||
def getWirelessNetworks(interface):
|
def getWirelessNetworks(interface):
|
||||||
# TODO: Make this oneliner pritter to check if the interface is scanning or not.
|
# TODO: Make this oneliner pritter to check if the interface is scanning or not.
|
||||||
if not '_WIFI' in storage or interface not in storage['_WIFI'] or storage['_WIFI'][interface].get('scanning', False) is False:
|
if not '_WIFI' in storage or interface not in storage['_WIFI'] or storage['_WIFI'][interface].get('scanning', False) is False:
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from pathlib import Path
|
||||||
from .storage import storage
|
from .storage import storage
|
||||||
|
|
||||||
# TODO: use logging's built in levels instead.
|
# TODO: use logging's built in levels instead.
|
||||||
# Altough logging is threaded and I wish to avoid that.
|
# Although logging is threaded and I wish to avoid that.
|
||||||
# It's more Pythonistic or w/e you want to call it.
|
# It's more Pythonistic or w/e you want to call it.
|
||||||
class LOG_LEVELS:
|
class LOG_LEVELS:
|
||||||
Critical = 0b001
|
Critical = 0b001
|
||||||
|
|
@ -88,7 +88,7 @@ def log(*args, **kwargs):
|
||||||
# Attempt to colorize the output if supported
|
# Attempt to colorize the output if supported
|
||||||
# Insert default colors and override with **kwargs
|
# Insert default colors and override with **kwargs
|
||||||
if supports_color():
|
if supports_color():
|
||||||
kwargs = {'bg' : 'black', 'fg': 'white', **kwargs}
|
kwargs = {'fg': 'white', **kwargs}
|
||||||
string = stylize_output(string, **kwargs)
|
string = stylize_output(string, **kwargs)
|
||||||
|
|
||||||
# If a logfile is defined in storage,
|
# If a logfile is defined in storage,
|
||||||
|
|
|
||||||
|
|
@ -112,11 +112,11 @@ class Script():
|
||||||
|
|
||||||
if f"{self.profile}" in self.examples:
|
if f"{self.profile}" in self.examples:
|
||||||
return self.localize_path(self.examples[self.profile]['path'])
|
return self.localize_path(self.examples[self.profile]['path'])
|
||||||
# TODO: Redundant, the below block shouldnt be needed as profiles are stripped of their .py, but just in case for now:
|
# TODO: Redundant, the below block shouldn't be needed as profiles are stripped of their .py, but just in case for now:
|
||||||
elif f"{self.profile}.py" in self.examples:
|
elif f"{self.profile}.py" in self.examples:
|
||||||
return self.localize_path(self.examples[f"{self.profile}.py"]['path'])
|
return self.localize_path(self.examples[f"{self.profile}.py"]['path'])
|
||||||
|
|
||||||
# Path was not found in any known examples, check if it's an abolute path
|
# Path was not found in any known examples, check if it's an absolute path
|
||||||
if os.path.isfile(self.profile):
|
if os.path.isfile(self.profile):
|
||||||
return self.profile
|
return self.profile
|
||||||
|
|
||||||
|
|
@ -156,7 +156,7 @@ class Profile(Script):
|
||||||
|
|
||||||
def install(self):
|
def install(self):
|
||||||
# Before installing, revert any temporary changes to the namespace.
|
# Before installing, revert any temporary changes to the namespace.
|
||||||
# This ensures that the namespace during installation is the original initation namespace.
|
# This ensures that the namespace during installation is the original initiation namespace.
|
||||||
# (For instance awesome instead of aweosme.py or app-awesome.py)
|
# (For instance awesome instead of aweosme.py or app-awesome.py)
|
||||||
self.namespace = self.original_namespace
|
self.namespace = self.original_namespace
|
||||||
return self.execute()
|
return self.execute()
|
||||||
|
|
@ -177,6 +177,7 @@ class Profile(Script):
|
||||||
if hasattr(imported, '_prep_function'):
|
if hasattr(imported, '_prep_function'):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def has_post_install(self):
|
def has_post_install(self):
|
||||||
with open(self.path, 'r') as source:
|
with open(self.path, 'r') as source:
|
||||||
source_data = source.read()
|
source_data = source.read()
|
||||||
|
|
@ -193,6 +194,56 @@ class Profile(Script):
|
||||||
if hasattr(imported, '_post_install'):
|
if hasattr(imported, '_post_install'):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def is_top_level_profile(self):
|
||||||
|
with open(self.path, 'r') as source:
|
||||||
|
source_data = source.read()
|
||||||
|
|
||||||
|
# TODO: I imagine that there is probably a better way to write this.
|
||||||
|
return 'top_level_profile = True' in source_data
|
||||||
|
|
||||||
|
@property
|
||||||
|
def packages(self) -> list:
|
||||||
|
"""
|
||||||
|
Returns a list of packages baked into the profile definition.
|
||||||
|
If no package definition has been done, .packages() will return None.
|
||||||
|
"""
|
||||||
|
with open(self.path, 'r') as source:
|
||||||
|
source_data = source.read()
|
||||||
|
|
||||||
|
# Some crude safety checks, make sure the imported profile has
|
||||||
|
# a __name__ check before importing.
|
||||||
|
#
|
||||||
|
# If the requirements are met, import with .py in the namespace to not
|
||||||
|
# trigger a traditional:
|
||||||
|
# if __name__ == 'moduleName'
|
||||||
|
if '__name__' in source_data and '__packages__' in source_data:
|
||||||
|
with self.load_instructions(namespace=f"{self.namespace}.py") as imported:
|
||||||
|
if hasattr(imported, '__packages__'):
|
||||||
|
return imported.__packages__
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def has_post_install(self):
|
||||||
|
with open(self.path, 'r') as source:
|
||||||
|
source_data = source.read()
|
||||||
|
|
||||||
|
# Some crude safety checks, make sure the imported profile has
|
||||||
|
# a __name__ check and if so, check if it's got a _prep_function()
|
||||||
|
# we can call to ask for more user input.
|
||||||
|
#
|
||||||
|
# If the requirements are met, import with .py in the namespace to not
|
||||||
|
# trigger a traditional:
|
||||||
|
# if __name__ == 'moduleName'
|
||||||
|
if '__name__' in source_data and '_post_install' in source_data:
|
||||||
|
with self.load_instructions(namespace=f"{self.namespace}.py") as imported:
|
||||||
|
if hasattr(imported, '_post_install'):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_top_level_profile(self):
|
||||||
|
with open(self.path, 'r') as source:
|
||||||
|
source_data = source.read()
|
||||||
|
return 'top_level_profile = True' in source_data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def packages(self) -> list:
|
def packages(self) -> list:
|
||||||
"""
|
"""
|
||||||
|
|
@ -231,11 +282,11 @@ class Application(Profile):
|
||||||
|
|
||||||
if f"{self.profile}" in self.examples:
|
if f"{self.profile}" in self.examples:
|
||||||
return self.localize_path(self.examples[self.profile]['path'])
|
return self.localize_path(self.examples[self.profile]['path'])
|
||||||
# TODO: Redundant, the below block shouldnt be needed as profiles are stripped of their .py, but just in case for now:
|
# TODO: Redundant, the below block shouldn't be needed as profiles are stripped of their .py, but just in case for now:
|
||||||
elif f"{self.profile}.py" in self.examples:
|
elif f"{self.profile}.py" in self.examples:
|
||||||
return self.localize_path(self.examples[f"{self.profile}.py"]['path'])
|
return self.localize_path(self.examples[f"{self.profile}.py"]['path'])
|
||||||
|
|
||||||
# Path was not found in any known examples, check if it's an abolute path
|
# Path was not found in any known examples, check if it's an absolute path
|
||||||
if os.path.isfile(self.profile):
|
if os.path.isfile(self.profile):
|
||||||
return os.path.basename(self.profile)
|
return os.path.basename(self.profile)
|
||||||
|
|
||||||
|
|
@ -247,7 +298,7 @@ class Application(Profile):
|
||||||
|
|
||||||
def install(self):
|
def install(self):
|
||||||
# Before installing, revert any temporary changes to the namespace.
|
# Before installing, revert any temporary changes to the namespace.
|
||||||
# This ensures that the namespace during installation is the original initation namespace.
|
# This ensures that the namespace during installation is the original initiation namespace.
|
||||||
# (For instance awesome instead of aweosme.py or app-awesome.py)
|
# (For instance awesome instead of aweosme.py or app-awesome.py)
|
||||||
self.namespace = self.original_namespace
|
self.namespace = self.original_namespace
|
||||||
return self.execute()
|
return self.execute()
|
||||||
|
|
@ -7,6 +7,6 @@ def service_state(service_name: str):
|
||||||
if os.path.splitext(service_name)[1] != '.service':
|
if os.path.splitext(service_name)[1] != '.service':
|
||||||
service_name += '.service' # Just to be safe
|
service_name += '.service' # Just to be safe
|
||||||
|
|
||||||
state = b''.join(sys_command(f'systemctl show -p SubState --value {service_name}'))
|
state = b''.join(sys_command(f'systemctl show --no-pager -p SubState --value {service_name}', environment_vars={'SYSTEMD_COLORS' : '0'}))
|
||||||
|
|
||||||
return state.strip().decode('UTF-8')
|
return state.strip().decode('UTF-8')
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ storage = {
|
||||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'profiles'),
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'profiles'),
|
||||||
#os.path.abspath(f'{os.path.dirname(__file__)}/../examples')
|
#os.path.abspath(f'{os.path.dirname(__file__)}/../examples')
|
||||||
],
|
],
|
||||||
'UPSTREAM_URL' : 'https://raw.githubusercontent.com/Torxed/archinstall/master/profiles',
|
'UPSTREAM_URL' : 'https://raw.githubusercontent.com/archlinux/archinstall/master/profiles',
|
||||||
'PROFILE_DB' : None, # Used in cases when listing profiles is desired, not mandatory for direct profile grabing.
|
'PROFILE_DB' : None, # Used in cases when listing profiles is desired, not mandatory for direct profile grabing.
|
||||||
'LOG_PATH' : '/var/log/archinstall',
|
'LOG_PATH' : '/var/log/archinstall',
|
||||||
'LOG_FILE' : 'install.log',
|
'LOG_FILE' : 'install.log',
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
import getpass, pathlib, os, shutil
|
import getpass, pathlib, os, shutil, re
|
||||||
|
import sys, time, signal
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .profiles import Profile
|
from .profiles import Profile
|
||||||
from .locale_helpers import search_keyboard_layout
|
from .locale_helpers import search_keyboard_layout
|
||||||
from .output import log, LOG_LEVELS
|
from .output import log, LOG_LEVELS
|
||||||
from .storage import storage
|
from .storage import storage
|
||||||
from .networking import list_interfaces
|
from .networking import list_interfaces
|
||||||
|
from .general import sys_command
|
||||||
|
from .hardware import AVAILABLE_GFX_DRIVERS
|
||||||
|
|
||||||
## TODO: Some inconsistencies between the selection processes.
|
## TODO: Some inconsistencies between the selection processes.
|
||||||
## Some return the keys from the options, some the values?
|
## Some return the keys from the options, some the values?
|
||||||
|
|
@ -18,11 +21,56 @@ def get_terminal_width():
|
||||||
def get_longest_option(options):
|
def get_longest_option(options):
|
||||||
return max([len(x) for x in options])
|
return max([len(x) for x in options])
|
||||||
|
|
||||||
|
def check_for_correct_username(username):
|
||||||
|
if re.match(r'^[a-z_][a-z0-9_-]*\$?$', username) and len(username) <= 32:
|
||||||
|
return True
|
||||||
|
log(
|
||||||
|
"The username you entered is invalid. Try again",
|
||||||
|
level=LOG_LEVELS.Warning,
|
||||||
|
fg='red'
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def do_countdown():
|
||||||
|
SIG_TRIGGER = False
|
||||||
|
def kill_handler(sig, frame):
|
||||||
|
print()
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
def sig_handler(sig, frame):
|
||||||
|
global SIG_TRIGGER
|
||||||
|
SIG_TRIGGER = True
|
||||||
|
signal.signal(signal.SIGINT, kill_handler)
|
||||||
|
|
||||||
|
original_sigint_handler = signal.getsignal(signal.SIGINT)
|
||||||
|
signal.signal(signal.SIGINT, sig_handler)
|
||||||
|
|
||||||
|
for i in range(5, 0, -1):
|
||||||
|
print(f"{i}", end='')
|
||||||
|
|
||||||
|
for x in range(4):
|
||||||
|
sys.stdout.flush()
|
||||||
|
time.sleep(0.25)
|
||||||
|
print(".", end='')
|
||||||
|
|
||||||
|
if SIG_TRIGGER:
|
||||||
|
abort = input('\nDo you really want to abort (y/n)? ')
|
||||||
|
if abort.strip() != 'n':
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
if SIG_TRIGGER is False:
|
||||||
|
sys.stdin.read()
|
||||||
|
SIG_TRIGGER = False
|
||||||
|
signal.signal(signal.SIGINT, sig_handler)
|
||||||
|
print()
|
||||||
|
signal.signal(signal.SIGINT, original_sigint_handler)
|
||||||
|
return True
|
||||||
|
|
||||||
def get_password(prompt="Enter a password: "):
|
def get_password(prompt="Enter a password: "):
|
||||||
while (passwd := getpass.getpass(prompt)):
|
while (passwd := getpass.getpass(prompt)):
|
||||||
passwd_verification = getpass.getpass(prompt='And one more time for verification: ')
|
passwd_verification = getpass.getpass(prompt='And one more time for verification: ')
|
||||||
if passwd != passwd_verification:
|
if passwd != passwd_verification:
|
||||||
log(' * Passwords did not match * ', bg='black', fg='red')
|
log(' * Passwords did not match * ', fg='red')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if len(passwd.strip()) <= 0:
|
if len(passwd.strip()) <= 0:
|
||||||
|
|
@ -54,10 +102,12 @@ def ask_for_superuser_account(prompt='Create a required super-user with sudo pri
|
||||||
if not new_user and forced:
|
if not new_user and forced:
|
||||||
# TODO: make this text more generic?
|
# TODO: make this text more generic?
|
||||||
# It's only used to create the first sudo user when root is disabled in guided.py
|
# It's only used to create the first sudo user when root is disabled in guided.py
|
||||||
log(' * Since root is disabled, you need to create a least one (super) user!', bg='black', fg='red')
|
log(' * Since root is disabled, you need to create a least one (super) user!', fg='red')
|
||||||
continue
|
continue
|
||||||
elif not new_user and not forced:
|
elif not new_user and not forced:
|
||||||
raise UserError("No superuser was created.")
|
raise UserError("No superuser was created.")
|
||||||
|
elif not check_for_correct_username(new_user):
|
||||||
|
continue
|
||||||
|
|
||||||
password = get_password(prompt=f'Password for user {new_user}: ')
|
password = get_password(prompt=f'Password for user {new_user}: ')
|
||||||
return {new_user: {"!password" : password}}
|
return {new_user: {"!password" : password}}
|
||||||
|
|
@ -70,6 +120,8 @@ def ask_for_additional_users(prompt='Any additional users to install (leave blan
|
||||||
new_user = input(prompt).strip(' ')
|
new_user = input(prompt).strip(' ')
|
||||||
if not new_user:
|
if not new_user:
|
||||||
break
|
break
|
||||||
|
if not check_for_correct_username(new_user):
|
||||||
|
continue
|
||||||
password = get_password(prompt=f'Password for user {new_user}: ')
|
password = get_password(prompt=f'Password for user {new_user}: ')
|
||||||
|
|
||||||
if input("Should this user be a sudo (super) user (y/N): ").strip(' ').lower() in ('y', 'yes'):
|
if input("Should this user be a sudo (super) user (y/N): ").strip(' ').lower() in ('y', 'yes'):
|
||||||
|
|
@ -80,7 +132,9 @@ def ask_for_additional_users(prompt='Any additional users to install (leave blan
|
||||||
return users, super_users
|
return users, super_users
|
||||||
|
|
||||||
def ask_for_a_timezone():
|
def ask_for_a_timezone():
|
||||||
timezone = input('Enter a valid timezone (Example: Europe/Stockholm): ').strip()
|
timezone = input('Enter a valid timezone (examples: Europe/Stockholm, US/Eastern) or press enter to use UTC: ').strip()
|
||||||
|
if timezone == '':
|
||||||
|
timezone = 'UTC'
|
||||||
if (pathlib.Path("/usr")/"share"/"zoneinfo"/timezone).exists():
|
if (pathlib.Path("/usr")/"share"/"zoneinfo"/timezone).exists():
|
||||||
return timezone
|
return timezone
|
||||||
else:
|
else:
|
||||||
|
|
@ -90,6 +144,14 @@ def ask_for_a_timezone():
|
||||||
fg='red'
|
fg='red'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def ask_for_audio_selection():
|
||||||
|
audio = "pulseaudio" # Default for most desktop environments
|
||||||
|
pipewire_choice = input("Would you like to install pipewire instead of pulseaudio as the default audio server? [Y/n] ").lower()
|
||||||
|
if pipewire_choice in ("y", ""):
|
||||||
|
audio = "pipewire"
|
||||||
|
|
||||||
|
return audio
|
||||||
|
|
||||||
def ask_to_configure_network():
|
def ask_to_configure_network():
|
||||||
# Optionally configure one network interface.
|
# Optionally configure one network interface.
|
||||||
#while 1:
|
#while 1:
|
||||||
|
|
@ -110,7 +172,6 @@ def ask_to_configure_network():
|
||||||
log(
|
log(
|
||||||
"You need to enter a valid IP in IP-config mode.",
|
"You need to enter a valid IP in IP-config mode.",
|
||||||
level=LOG_LEVELS.Warning,
|
level=LOG_LEVELS.Warning,
|
||||||
bg='black',
|
|
||||||
fg='red'
|
fg='red'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -127,7 +188,7 @@ def ask_to_configure_network():
|
||||||
elif nic:
|
elif nic:
|
||||||
return nic
|
return nic
|
||||||
|
|
||||||
return None
|
return {}
|
||||||
|
|
||||||
def ask_for_disk_layout():
|
def ask_for_disk_layout():
|
||||||
options = {
|
options = {
|
||||||
|
|
@ -153,7 +214,7 @@ def ask_for_main_filesystem_format():
|
||||||
def generic_select(options, input_text="Select one of the above by index or absolute value: ", sort=True):
|
def generic_select(options, input_text="Select one of the above by index or absolute value: ", sort=True):
|
||||||
"""
|
"""
|
||||||
A generic select function that does not output anything
|
A generic select function that does not output anything
|
||||||
other than the options and their indexs. As an example:
|
other than the options and their indexes. As an example:
|
||||||
|
|
||||||
generic_select(["first", "second", "third option"])
|
generic_select(["first", "second", "third option"])
|
||||||
1: first
|
1: first
|
||||||
|
|
@ -173,7 +234,7 @@ def generic_select(options, input_text="Select one of the above by index or abso
|
||||||
return None
|
return None
|
||||||
elif selected_option.isdigit():
|
elif selected_option.isdigit():
|
||||||
selected_option = int(selected_option)
|
selected_option = int(selected_option)
|
||||||
if selected_option >= len(options):
|
if selected_option > len(options):
|
||||||
raise RequirementError(f'Selected option "{selected_option}" is out of range')
|
raise RequirementError(f'Selected option "{selected_option}" is out of range')
|
||||||
selected_option = options[selected_option]
|
selected_option = options[selected_option]
|
||||||
elif selected_option in options:
|
elif selected_option in options:
|
||||||
|
|
@ -198,8 +259,10 @@ def select_disk(dict_o_disks):
|
||||||
if len(drives) >= 1:
|
if len(drives) >= 1:
|
||||||
for index, drive in enumerate(drives):
|
for index, drive in enumerate(drives):
|
||||||
print(f"{index}: {drive} ({dict_o_disks[drive]['size'], dict_o_disks[drive].device, dict_o_disks[drive]['label']})")
|
print(f"{index}: {drive} ({dict_o_disks[drive]['size'], dict_o_disks[drive].device, dict_o_disks[drive]['label']})")
|
||||||
drive = input('Select one of the above disks (by number or full path): ')
|
drive = input('Select one of the above disks (by number or full path) or write /mnt to skip partitioning: ')
|
||||||
if drive.isdigit():
|
if drive.strip() == '/mnt':
|
||||||
|
return None
|
||||||
|
elif drive.isdigit():
|
||||||
drive = int(drive)
|
drive = int(drive)
|
||||||
if drive >= len(drives):
|
if drive >= len(drives):
|
||||||
raise DiskError(f'Selected option "{drive}" is out of range')
|
raise DiskError(f'Selected option "{drive}" is out of range')
|
||||||
|
|
@ -262,6 +325,8 @@ def select_language(options, show_only_country_codes=True):
|
||||||
:return: The language/dictionary key of the selected language
|
:return: The language/dictionary key of the selected language
|
||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
|
DEFAULT_KEYBOARD_LANGUAGE = 'us'
|
||||||
|
|
||||||
if show_only_country_codes:
|
if show_only_country_codes:
|
||||||
languages = sorted([language for language in list(options) if len(language) == 2])
|
languages = sorted([language for language in list(options) if len(language) == 2])
|
||||||
else:
|
else:
|
||||||
|
|
@ -271,9 +336,12 @@ def select_language(options, show_only_country_codes=True):
|
||||||
for index, language in enumerate(languages):
|
for index, language in enumerate(languages):
|
||||||
print(f"{index}: {language}")
|
print(f"{index}: {language}")
|
||||||
|
|
||||||
print(' -- You can enter ? or help to search for more languages --')
|
print(' -- You can enter ? or help to search for more languages, or skip to use US layout --')
|
||||||
selected_language = input('Select one of the above keyboard languages (by number or full name): ')
|
selected_language = input('Select one of the above keyboard languages (by number or full name): ')
|
||||||
if selected_language.lower() in ('?', 'help'):
|
|
||||||
|
if len(selected_language.strip()) == 0:
|
||||||
|
return DEFAULT_KEYBOARD_LANGUAGE
|
||||||
|
elif selected_language.lower() in ('?', 'help'):
|
||||||
while True:
|
while True:
|
||||||
filter_string = input('Search for layout containing (example: "sv-"): ')
|
filter_string = input('Search for layout containing (example: "sv-"): ')
|
||||||
new_options = list(search_keyboard_layout(filter_string))
|
new_options = list(search_keyboard_layout(filter_string))
|
||||||
|
|
@ -286,6 +354,7 @@ def select_language(options, show_only_country_codes=True):
|
||||||
|
|
||||||
elif selected_language.isdigit() and (pos := int(selected_language)) <= len(languages)-1:
|
elif selected_language.isdigit() and (pos := int(selected_language)) <= len(languages)-1:
|
||||||
selected_language = languages[pos]
|
selected_language = languages[pos]
|
||||||
|
return selected_language
|
||||||
# I'm leaving "options" on purpose here.
|
# I'm leaving "options" on purpose here.
|
||||||
# Since languages possibly contains a filtered version of
|
# Since languages possibly contains a filtered version of
|
||||||
# all possible language layouts, and we might want to write
|
# all possible language layouts, and we might want to write
|
||||||
|
|
@ -293,9 +362,9 @@ def select_language(options, show_only_country_codes=True):
|
||||||
# go through the search step.
|
# go through the search step.
|
||||||
elif selected_language in options:
|
elif selected_language in options:
|
||||||
selected_language = options[options.index(selected_language)]
|
selected_language = options[options.index(selected_language)]
|
||||||
|
return selected_language
|
||||||
else:
|
else:
|
||||||
RequirementError("Selected language does not exist.")
|
raise RequirementError("Selected language does not exist.")
|
||||||
return selected_language
|
|
||||||
|
|
||||||
raise RequirementError("Selecting languages require a least one language to be given as an option.")
|
raise RequirementError("Selecting languages require a least one language to be given as an option.")
|
||||||
|
|
||||||
|
|
@ -319,26 +388,64 @@ def select_mirror_regions(mirrors, show_top_mirrors=True):
|
||||||
selected_mirrors = {}
|
selected_mirrors = {}
|
||||||
|
|
||||||
if len(regions) >= 1:
|
if len(regions) >= 1:
|
||||||
print_large_list(regions, margin_bottom=2)
|
print_large_list(regions, margin_bottom=4)
|
||||||
|
|
||||||
print(' -- You can skip this step by leaving the option blank --')
|
print(' -- You can skip this step by leaving the option blank --')
|
||||||
selected_mirror = input('Select one of the above regions to download packages from (by number or full name): ')
|
selected_mirror = input('Select one of the above regions to download packages from (by number or full name): ')
|
||||||
if len(selected_mirror.strip()) == 0:
|
if len(selected_mirror.strip()) == 0:
|
||||||
|
# Returning back empty options which can be both used to
|
||||||
|
# do "if x:" logic as well as do `x.get('mirror', {}).get('sub', None)` chaining
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
elif selected_mirror.isdigit() and (pos := int(selected_mirror)) <= len(regions)-1:
|
elif selected_mirror.isdigit() and int(selected_mirror) <= len(regions)-1:
|
||||||
|
# I'm leaving "mirrors" on purpose here.
|
||||||
|
# Since region possibly contains a known region of
|
||||||
|
# all possible regions, and we might want to write
|
||||||
|
# for instance Sweden (if we know that exists) without having to
|
||||||
|
# go through the search step.
|
||||||
region = regions[int(selected_mirror)]
|
region = regions[int(selected_mirror)]
|
||||||
selected_mirrors[region] = mirrors[region]
|
selected_mirrors[region] = mirrors[region]
|
||||||
# I'm leaving "mirrors" on purpose here.
|
|
||||||
# Since region possibly contains a known region of
|
|
||||||
# all possible regions, and we might want to write
|
|
||||||
# for instance Sweden (if we know that exists) without having to
|
|
||||||
# go through the search step.
|
|
||||||
elif selected_mirror in mirrors:
|
elif selected_mirror in mirrors:
|
||||||
selected_mirrors[selected_mirror] = mirrors[selected_mirror]
|
selected_mirrors[selected_mirror] = mirrors[selected_mirror]
|
||||||
else:
|
else:
|
||||||
RequirementError("Selected region does not exist.")
|
raise RequirementError("Selected region does not exist.")
|
||||||
|
|
||||||
return selected_mirrors
|
return selected_mirrors
|
||||||
|
|
||||||
raise RequirementError("Selecting mirror region require a least one region to be given as an option.")
|
raise RequirementError("Selecting mirror region require a least one region to be given as an option.")
|
||||||
|
|
||||||
|
def select_driver(options=AVAILABLE_GFX_DRIVERS):
|
||||||
|
"""
|
||||||
|
Some what convoluted function, which's job is simple.
|
||||||
|
Select a graphics driver from a pre-defined set of popular options.
|
||||||
|
|
||||||
|
(The template xorg is for beginner users, not advanced, and should
|
||||||
|
there for appeal to the general public first and edge cases later)
|
||||||
|
"""
|
||||||
|
if len(options) >= 1:
|
||||||
|
lspci = sys_command(f'/usr/bin/lspci')
|
||||||
|
for line in lspci.trace_log.split(b'\r\n'):
|
||||||
|
if b' vga ' in line.lower():
|
||||||
|
if b'nvidia' in line.lower():
|
||||||
|
print(' ** nvidia card detected, suggested driver: nvidia **')
|
||||||
|
elif b'amd' in line.lower():
|
||||||
|
print(' ** AMD card detected, suggested driver: AMD / ATI **')
|
||||||
|
|
||||||
|
selected_driver = generic_select(options, input_text="Select your graphics card driver: ", sort=True)
|
||||||
|
initial_option = selected_driver
|
||||||
|
|
||||||
|
if type(options[initial_option]) == dict:
|
||||||
|
driver_options = sorted(options[initial_option].keys())
|
||||||
|
|
||||||
|
selected_driver_package_group = generic_select(driver_options, input_text=f"Which driver-type do you want for {initial_option}: ")
|
||||||
|
if selected_driver_package_group in options[initial_option].keys():
|
||||||
|
print(options[initial_option][selected_driver_package_group])
|
||||||
|
selected_driver = options[initial_option][selected_driver_package_group]
|
||||||
|
else:
|
||||||
|
raise RequirementError(f"Selected driver-type does not exist for {initial_option}.")
|
||||||
|
|
||||||
|
return selected_driver_package_group
|
||||||
|
|
||||||
|
return selected_driver
|
||||||
|
|
||||||
|
raise RequirementError("Selecting drivers require a least one profile to be given as an option.")
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ Packages
|
||||||
========
|
========
|
||||||
|
|
||||||
.. autofunction:: archinstall.find_package
|
.. autofunction:: archinstall.find_package
|
||||||
|
Be
|
||||||
.. autofunction:: archinstall.find_packages
|
.. autofunction:: archinstall.find_packages
|
||||||
|
|
||||||
Locale related
|
Locale related
|
||||||
|
|
@ -91,3 +91,13 @@ Exceptions
|
||||||
.. autofunction:: archinstall.ProfileError
|
.. autofunction:: archinstall.ProfileError
|
||||||
|
|
||||||
.. autofunction:: archinstall.SysCallError
|
.. autofunction:: archinstall.SysCallError
|
||||||
|
|
||||||
|
.. autofunction:: archinstall.ProfileNotFound
|
||||||
|
|
||||||
|
.. autofunction:: archinstall.HardwareIncompatibilityError
|
||||||
|
|
||||||
|
.. autofunction:: archinstall.PermissionError
|
||||||
|
|
||||||
|
.. autofunction:: archinstall.UserError
|
||||||
|
|
||||||
|
.. autofunction:: archinstall.ServiceException
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
.. _examples.python:
|
.. _examples.binary:
|
||||||
|
|
||||||
Binary executable
|
Binary executable
|
||||||
=================
|
=================
|
||||||
|
|
@ -11,7 +11,7 @@ It's compiled using `nuitka <https://nuitka.net/>`_ with the flag `--standalone`
|
||||||
Executing the binary
|
Executing the binary
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
As an example we'll use the `guided <https://github.com/Torxed/archinstall/blob/master/examples/guided.py>`_ installer.
|
As an example we'll use the `guided <https://github.com/archlinux/archinstall/blob/master/examples/guided.py>`_ installer.
|
||||||
To run the `guided` installed, all you have to do *(after installing or compiling the binary)*, is run:
|
To run the `guided` installed, all you have to do *(after installing or compiling the binary)*, is run:
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Discord
|
Discord
|
||||||
=======
|
=======
|
||||||
|
|
||||||
There's a discord channel which is frequent by some `contributors <https://github.com/Torxed/archinstall/graphs/contributors>`_.
|
There's a discord channel which is frequent by some `contributors <https://github.com/archlinux/archinstall/graphs/contributors>`_.
|
||||||
|
|
||||||
To join the server, head over to `https://discord.gg/cqXU88y <https://discord.gg/cqXU88y>`_'s server and join in.
|
To join the server, head over to `https://discord.gg/cqXU88y <https://discord.gg/cqXU88y>`_'s server and join in.
|
||||||
There's not many rules other than common sense and treat others with respect.
|
There's not many rules other than common sense and treat others with respect.
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Issue tracker & bugs
|
Issue tracker & bugs
|
||||||
====================
|
====================
|
||||||
|
|
||||||
Issues and bugs should be reported over at `https://github.com/Torxed/archinstall/issues <https://github.com/Torxed/archinstall/issues>`_.
|
Issues and bugs should be reported over at `https://github.com/archlinux/archinstall/issues <https://github.com/Torxed/archinstall/issues>`_.
|
||||||
|
|
||||||
General questions, enhancements and security issues can be reported over there too.
|
General questions, enhancements and security issues can be reported over there too.
|
||||||
For quick issues or if you need help, head over the to the Discord server which has a help channel.
|
For quick issues or if you need help, head over the to the Discord server which has a help channel.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ python-archinstall Documentation
|
||||||
================================
|
================================
|
||||||
|
|
||||||
| **python-archinstall** *(or, archinstall for short)* is a helper library to install Arch Linux and manage services, packages and other things.
|
| **python-archinstall** *(or, archinstall for short)* is a helper library to install Arch Linux and manage services, packages and other things.
|
||||||
| It comes packaged with different pre-configured installers, such as the :ref:`guided <installing.guided>` installer.
|
| It comes packaged with different pre-configured installers, such as the `Guided installation`_ installer.
|
||||||
|
|
|
|
||||||
| A demo can be viewed here: `https://www.youtube.com/watch?v=9Xt7X_Iqg6E <https://www.youtube.com/watch?v=9Xt7X_Iqg6E>`_ which uses the default guided installer.
|
| A demo can be viewed here: `https://www.youtube.com/watch?v=9Xt7X_Iqg6E <https://www.youtube.com/watch?v=9Xt7X_Iqg6E>`_ which uses the default guided installer.
|
||||||
|
|
||||||
|
|
@ -46,7 +46,8 @@ Some of the features of Archinstall are:
|
||||||
..
|
..
|
||||||
examples/scripting
|
examples/scripting
|
||||||
|
|
||||||
.. toctree::
|
..
|
||||||
|
.. toctree::
|
||||||
:maxdepth: 3
|
:maxdepth: 3
|
||||||
:caption: Programming Guide
|
:caption: Programming Guide
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
.. _installing.binary
|
.. _installing.binary:
|
||||||
|
|
||||||
Binary executable
|
Binary executable
|
||||||
=================
|
=================
|
||||||
|
|
||||||
Archinstall can be compiled into a standalone executable.
|
Archinstall can be compiled into a standalone executable.
|
||||||
For Arch Linux based systems, there's a package for this called `archinstall <https://archlinux.life/>`_.
|
For Arch Linux based systems, there's a package for this called `archinstall <https://archlinux.org/packages/extra/any/archinstall/>`_.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
This is not required if you're running archinstall on a pre-built ISO. The installation is only required if you're creating your own scripted installations.
|
This is not required if you're running archinstall on a pre-built ISO. The installation is only required if you're creating your own scripted installations.
|
||||||
|
|
@ -21,7 +21,7 @@ Archinstall is on the `official repositories <https://wiki.archlinux.org/index.p
|
||||||
Using PKGBUILD
|
Using PKGBUILD
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
The `source <https://github.com/Torxed/archinstall>`_ contains a binary `PKGBUILD <https://github.com/Torxed/archinstall/tree/master/PKGBUILD/archinstall>`_ which can be either copied straight off the website. Or cloned using `git clone https://github.com/Torxed/archinstall`.
|
The `source <https://github.com/archlinux/archinstall>`_ contains a binary `PKGBUILD <https://github.com/Torxed/archinstall/tree/master/PKGBUILD/archinstall>`_ which can be either copied straight off the website. Or cloned using `git clone https://github.com/Torxed/archinstall`.
|
||||||
|
|
||||||
Once you've obtained the `PKGBUILD`, building it is pretty straight forward.
|
Once you've obtained the `PKGBUILD`, building it is pretty straight forward.
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ Which should produce a `archinstall-X.x.z-1.pkg.tar.zst` that can be installed u
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
For a complete guide on the build process, please consult the wiki on `PKGBUILD <https://wiki.archlinux.org/index.php/PKGBUILD>`_.
|
For a complete guide on the build process, please consult the `PKGBUILD on ArchWiki <https://wiki.archlinux.org/index.php/PKGBUILD>`_.
|
||||||
|
|
||||||
Manual compilation
|
Manual compilation
|
||||||
------------------
|
------------------
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ Default is :code:`auto detect best mirror`
|
||||||
|
|
||||||
As an example:
|
As an example:
|
||||||
|
|
||||||
* :code:`Sweden` *(wich a capital :code:`S`)* will only use mirrors from Sweden.
|
* :code:`Sweden` *(with a capital :code:`S`)* will only use mirrors from Sweden.
|
||||||
|
|
||||||
Selection of drive
|
Selection of drive
|
||||||
------------------
|
------------------
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ Or you can clone it, we'll clone it here but both methods work the same.
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
git clone https://github.com/Torxed/archinstall
|
git clone https://github.com/archlinux/archinstall
|
||||||
|
|
||||||
Either you can move the folder into your project and simply do
|
Either you can move the folder into your project and simply do
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,28 @@
|
||||||
# Pull Request Template
|
🚨 PR Guidelines:
|
||||||
|
|
||||||
Make sure you've checked out the [contribution guideline](https://github.com/Torxed/archinstall/blob/master/CONTRIBUTING.md).<br>
|
# New features *(v2.2.0)*
|
||||||
Most of the guidelines are not enforced, but is heavily encouraged.
|
|
||||||
|
|
||||||
## Description
|
Merge new features in to `torxed-v2.2.0`.<br>
|
||||||
|
This branch is designated for potential breaking changes, added complexity and new functionality.
|
||||||
|
|
||||||
Please include a summary of the change and which issue is fixed.<br>
|
# Bug fixes *(v2.1.4)*
|
||||||
It is also helpful to add links to online documentation or to the implementation of the code you are changing.
|
|
||||||
|
|
||||||
## Bugs and Issues
|
Merge against `master` for bug fixes and anything that improves stability and quality of life.<br>
|
||||||
|
This excludes:
|
||||||
|
* New functionality
|
||||||
|
* Added complexity
|
||||||
|
* Breaking changes
|
||||||
|
|
||||||
If this pull-request fixes an issue or a bug, please mention the issues with the approriate issue referece *(Example: #8)*.
|
Any changes to `master` automatically gets pulled in to `torxed-v2.2.0` to avoid merge hell.
|
||||||
|
|
||||||
## How Has This Been Tested?
|
# Describe your PR
|
||||||
|
|
||||||
If possible, mention any tests you have made with the current code base included in the pull-requests.<br>
|
If the changes has been discussed in an Issue, please tag it so we can backtrace from the Issue later on.<br>
|
||||||
Any core-developer will also run tests, but this helps speed things up. Below is a template that can be used:
|
If the PR is larger than ~20 lines, please describe it here unless described in an issue.
|
||||||
|
|
||||||
As an example:
|
# Testing
|
||||||
|
|
||||||
**Test Configuration**:
|
Any new feature or stability improvement should be tested if possible.
|
||||||
* Hardware: VirtualBox 6.1
|
Please follow the test instructions at the bottom of the README.
|
||||||
* Specific steps: Ran installer with additional packages `nano` and `wget`
|
|
||||||
|
|
||||||
## Checklist:
|
*These PR guidelines will change after 2021-05-01, which is when `v2.1.4` gets onto the new ISO*
|
||||||
|
|
||||||
- [ ] My code follows the style guidelines of this project
|
|
||||||
- [ ] I have performed a self-review of my own code to the best of my abilities
|
|
||||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
|
||||||
- [ ] I have made corresponding changes to the documentation where possible/if applicable
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,7 @@
|
||||||
import getpass, time, json, sys, signal, os
|
import getpass, time, json, os
|
||||||
import archinstall
|
import archinstall
|
||||||
from archinstall.lib.hardware import hasUEFI
|
from archinstall.lib.hardware import hasUEFI
|
||||||
|
from archinstall.lib.profiles import Profile
|
||||||
"""
|
|
||||||
This signal-handler chain (and global variable)
|
|
||||||
is used to trigger the "Are you sure you want to abort?" question further down.
|
|
||||||
It might look a bit odd, but have a look at the line: "if SIG_TRIGGER:"
|
|
||||||
"""
|
|
||||||
SIG_TRIGGER = False
|
|
||||||
def kill_handler(sig, frame):
|
|
||||||
print()
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
def sig_handler(sig, frame):
|
|
||||||
global SIG_TRIGGER
|
|
||||||
SIG_TRIGGER = True
|
|
||||||
signal.signal(signal.SIGINT, kill_handler)
|
|
||||||
|
|
||||||
original_sigint_handler = signal.getsignal(signal.SIGINT)
|
|
||||||
signal.signal(signal.SIGINT, sig_handler)
|
|
||||||
|
|
||||||
if archinstall.arguments.get('help'):
|
if archinstall.arguments.get('help'):
|
||||||
print("See `man archinstall` for help.")
|
print("See `man archinstall` for help.")
|
||||||
|
|
@ -31,7 +14,12 @@ def ask_user_questions():
|
||||||
will we continue with the actual installation steps.
|
will we continue with the actual installation steps.
|
||||||
"""
|
"""
|
||||||
if not archinstall.arguments.get('keyboard-language', None):
|
if not archinstall.arguments.get('keyboard-language', None):
|
||||||
archinstall.arguments['keyboard-language'] = archinstall.select_language(archinstall.list_keyboard_languages()).strip()
|
while True:
|
||||||
|
try:
|
||||||
|
archinstall.arguments['keyboard-language'] = archinstall.select_language(archinstall.list_keyboard_languages()).strip()
|
||||||
|
break
|
||||||
|
except archinstall.RequirementError as err:
|
||||||
|
archinstall.log(err, fg="red")
|
||||||
|
|
||||||
# Before continuing, set the preferred keyboard layout/language in the current terminal.
|
# Before continuing, set the preferred keyboard layout/language in the current terminal.
|
||||||
# This will just help the user with the next following questions.
|
# This will just help the user with the next following questions.
|
||||||
|
|
@ -40,7 +28,12 @@ def ask_user_questions():
|
||||||
|
|
||||||
# Set which region to download packages from during the installation
|
# Set which region to download packages from during the installation
|
||||||
if not archinstall.arguments.get('mirror-region', None):
|
if not archinstall.arguments.get('mirror-region', None):
|
||||||
archinstall.arguments['mirror-region'] = archinstall.select_mirror_regions(archinstall.list_mirrors())
|
while True:
|
||||||
|
try:
|
||||||
|
archinstall.arguments['mirror-region'] = archinstall.select_mirror_regions(archinstall.list_mirrors())
|
||||||
|
break
|
||||||
|
except archinstall.RequirementError as e:
|
||||||
|
archinstall.log(e, fg="red")
|
||||||
else:
|
else:
|
||||||
selected_region = archinstall.arguments['mirror-region']
|
selected_region = archinstall.arguments['mirror-region']
|
||||||
archinstall.arguments['mirror-region'] = {selected_region : archinstall.list_mirrors()[selected_region]}
|
archinstall.arguments['mirror-region'] = {selected_region : archinstall.list_mirrors()[selected_region]}
|
||||||
|
|
@ -51,15 +44,17 @@ def ask_user_questions():
|
||||||
archinstall.arguments['harddrive'] = archinstall.BlockDevice(archinstall.arguments['harddrive'])
|
archinstall.arguments['harddrive'] = archinstall.BlockDevice(archinstall.arguments['harddrive'])
|
||||||
else:
|
else:
|
||||||
archinstall.arguments['harddrive'] = archinstall.select_disk(archinstall.all_disks())
|
archinstall.arguments['harddrive'] = archinstall.select_disk(archinstall.all_disks())
|
||||||
|
if archinstall.arguments['harddrive'] is None:
|
||||||
|
archinstall.arguments['target-mount'] = '/mnt'
|
||||||
|
|
||||||
# Perform a quick sanity check on the selected harddrive.
|
# Perform a quick sanity check on the selected harddrive.
|
||||||
# 1. Check if it has partitions
|
# 1. Check if it has partitions
|
||||||
# 3. Check that we support the current partitions
|
# 3. Check that we support the current partitions
|
||||||
# 2. If so, ask if we should keep them or wipe everything
|
# 2. If so, ask if we should keep them or wipe everything
|
||||||
if archinstall.arguments['harddrive'].has_partitions():
|
if archinstall.arguments['harddrive'] and archinstall.arguments['harddrive'].has_partitions():
|
||||||
archinstall.log(f"{archinstall.arguments['harddrive']} contains the following partitions:", fg='yellow')
|
archinstall.log(f"{archinstall.arguments['harddrive']} contains the following partitions:", fg='yellow')
|
||||||
|
|
||||||
# We curate a list pf supported paritions
|
# We curate a list pf supported partitions
|
||||||
# and print those that we don't support.
|
# and print those that we don't support.
|
||||||
partition_mountpoints = {}
|
partition_mountpoints = {}
|
||||||
for partition in archinstall.arguments['harddrive']:
|
for partition in archinstall.arguments['harddrive']:
|
||||||
|
|
@ -70,7 +65,7 @@ def ask_user_questions():
|
||||||
except archinstall.UnknownFilesystemFormat as err:
|
except archinstall.UnknownFilesystemFormat as err:
|
||||||
archinstall.log(f" {partition} (Filesystem not supported)", fg='red')
|
archinstall.log(f" {partition} (Filesystem not supported)", fg='red')
|
||||||
|
|
||||||
# We then ask what to do with the paritions.
|
# We then ask what to do with the partitions.
|
||||||
if (option := archinstall.ask_for_disk_layout()) == 'abort':
|
if (option := archinstall.ask_for_disk_layout()) == 'abort':
|
||||||
archinstall.log(f"Safely aborting the installation. No changes to the disk or system has been made.")
|
archinstall.log(f"Safely aborting the installation. No changes to the disk or system has been made.")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
@ -90,7 +85,7 @@ def ask_user_questions():
|
||||||
mountpoint = input(f"Enter a mount-point for {partition}: ").strip(' ')
|
mountpoint = input(f"Enter a mount-point for {partition}: ").strip(' ')
|
||||||
if len(mountpoint):
|
if len(mountpoint):
|
||||||
|
|
||||||
# Get a valid & supported filesystem for the parition:
|
# Get a valid & supported filesystem for the partition:
|
||||||
while 1:
|
while 1:
|
||||||
new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ')
|
new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ')
|
||||||
if len(new_filesystem) <= 0:
|
if len(new_filesystem) <= 0:
|
||||||
|
|
@ -113,7 +108,7 @@ def ask_user_questions():
|
||||||
try:
|
try:
|
||||||
partition.format(new_filesystem, path='/dev/null', log_formating=False, allow_formatting=True)
|
partition.format(new_filesystem, path='/dev/null', log_formating=False, allow_formatting=True)
|
||||||
except archinstall.UnknownFilesystemFormat:
|
except archinstall.UnknownFilesystemFormat:
|
||||||
archinstall.log(f"Selected filesystem is not supported yet. If you want archinstall to support '{new_filesystem}', please create a issue-ticket suggesting it on github at https://github.com/Torxed/archinstall/issues.")
|
archinstall.log(f"Selected filesystem is not supported yet. If you want archinstall to support '{new_filesystem}', please create a issue-ticket suggesting it on github at https://github.com/archlinux/archinstall/issues.")
|
||||||
archinstall.log(f"Until then, please enter another supported filesystem.")
|
archinstall.log(f"Until then, please enter another supported filesystem.")
|
||||||
continue
|
continue
|
||||||
except archinstall.SysCallError:
|
except archinstall.SysCallError:
|
||||||
|
|
@ -121,7 +116,7 @@ def ask_user_questions():
|
||||||
# But that means our .format() function supported it.
|
# But that means our .format() function supported it.
|
||||||
break
|
break
|
||||||
|
|
||||||
# When we've selected all three criterias,
|
# When we've selected all three criteria,
|
||||||
# We can safely mark the partition for formatting and where to mount it.
|
# We can safely mark the partition for formatting and where to mount it.
|
||||||
# TODO: allow_formatting might be redundant since target_mountpoint should only be
|
# TODO: allow_formatting might be redundant since target_mountpoint should only be
|
||||||
# set if we actually want to format it anyway.
|
# set if we actually want to format it anyway.
|
||||||
|
|
@ -135,14 +130,14 @@ def ask_user_questions():
|
||||||
elif option == 'format-all':
|
elif option == 'format-all':
|
||||||
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
||||||
archinstall.arguments['harddrive'].keep_partitions = False
|
archinstall.arguments['harddrive'].keep_partitions = False
|
||||||
else:
|
elif archinstall.arguments['harddrive']:
|
||||||
# If the drive doesn't have any partitions, safely mark the disk with keep_partitions = False
|
# If the drive doesn't have any partitions, safely mark the disk with keep_partitions = False
|
||||||
# and ask the user for a root filesystem.
|
# and ask the user for a root filesystem.
|
||||||
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
||||||
archinstall.arguments['harddrive'].keep_partitions = False
|
archinstall.arguments['harddrive'].keep_partitions = False
|
||||||
|
|
||||||
# Get disk encryption password (or skip if blank)
|
# Get disk encryption password (or skip if blank)
|
||||||
if not archinstall.arguments.get('!encryption-password', None):
|
if archinstall.arguments['harddrive'] and archinstall.arguments.get('!encryption-password', None) is None:
|
||||||
if (passwd := archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ')):
|
if (passwd := archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ')):
|
||||||
archinstall.arguments['!encryption-password'] = passwd
|
archinstall.arguments['!encryption-password'] = passwd
|
||||||
archinstall.arguments['harddrive'].encryption_password = archinstall.arguments['!encryption-password']
|
archinstall.arguments['harddrive'].encryption_password = archinstall.arguments['!encryption-password']
|
||||||
|
|
@ -167,31 +162,50 @@ def ask_user_questions():
|
||||||
|
|
||||||
# Ask for archinstall-specific profiles (such as desktop environments etc)
|
# Ask for archinstall-specific profiles (such as desktop environments etc)
|
||||||
if not archinstall.arguments.get('profile', None):
|
if not archinstall.arguments.get('profile', None):
|
||||||
archinstall.arguments['profile'] = archinstall.select_profile(archinstall.list_profiles())
|
archinstall.arguments['profile'] = archinstall.select_profile(filter(lambda profile: (Profile(None, profile).is_top_level_profile()), archinstall.list_profiles()))
|
||||||
else:
|
else:
|
||||||
archinstall.arguments['profile'] = archinstall.list_profiles()[archinstall.arguments['profile']]
|
archinstall.arguments['profile'] = archinstall.list_profiles()[archinstall.arguments['profile']]
|
||||||
|
|
||||||
# Check the potentially selected profiles preperations to get early checks if some additional questions are needed.
|
# Check the potentially selected profiles preparations to get early checks if some additional questions are needed.
|
||||||
if archinstall.arguments['profile'] and archinstall.arguments['profile'].has_prep_function():
|
if archinstall.arguments['profile'] and archinstall.arguments['profile'].has_prep_function():
|
||||||
with archinstall.arguments['profile'].load_instructions(namespace=f"{archinstall.arguments['profile'].namespace}.py") as imported:
|
with archinstall.arguments['profile'].load_instructions(namespace=f"{archinstall.arguments['profile'].namespace}.py") as imported:
|
||||||
if not imported._prep_function():
|
if not imported._prep_function():
|
||||||
archinstall.log(
|
archinstall.log(
|
||||||
' * Profile\'s preparation requirements was not fulfilled.',
|
' * Profile\'s preparation requirements was not fulfilled.',
|
||||||
bg='black',
|
|
||||||
fg='red'
|
fg='red'
|
||||||
)
|
)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
# Additional packages (with some light weight error handling for invalid package names)
|
# Ask about audio server selection if one is not already set
|
||||||
if not archinstall.arguments.get('packages', None):
|
if not archinstall.arguments.get('audio', None):
|
||||||
archinstall.arguments['packages'] = [package for package in input('Write additional packages to install (space separated, leave blank to skip): ').split(' ') if len(package)]
|
|
||||||
|
|
||||||
# Verify packages that were given
|
# only ask for audio server selection on a desktop profile
|
||||||
try:
|
if str(archinstall.arguments['profile']) == 'Profile(desktop)':
|
||||||
archinstall.validate_package_list(archinstall.arguments['packages'])
|
archinstall.arguments['audio'] = archinstall.ask_for_audio_selection()
|
||||||
except archinstall.RequirementError as e:
|
else:
|
||||||
archinstall.log(e, fg='red')
|
# packages installed by a profile may depend on audio and something may get installed anyways, not much we can do about that.
|
||||||
exit(1)
|
# we will not try to remove packages post-installation to not have audio, as that may cause multiple issues
|
||||||
|
archinstall.arguments['audio'] = None
|
||||||
|
|
||||||
|
# Additional packages (with some light weight error handling for invalid package names)
|
||||||
|
while True:
|
||||||
|
if not archinstall.arguments.get('packages', None):
|
||||||
|
print("Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed.")
|
||||||
|
print("If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt.")
|
||||||
|
archinstall.arguments['packages'] = [package for package in input('Write additional packages to install (space separated, leave blank to skip): ').split(' ') if len(package)]
|
||||||
|
|
||||||
|
if len(archinstall.arguments['packages']):
|
||||||
|
# Verify packages that were given
|
||||||
|
try:
|
||||||
|
archinstall.log(f"Verifying that additional packages exist (this might take a few seconds)")
|
||||||
|
archinstall.validate_package_list(archinstall.arguments['packages'])
|
||||||
|
break
|
||||||
|
except archinstall.RequirementError as e:
|
||||||
|
archinstall.log(e, fg='red')
|
||||||
|
archinstall.arguments['packages'] = None # Clear the packages to trigger a new input question
|
||||||
|
else:
|
||||||
|
# no additional packages were selected, which we'll allow
|
||||||
|
break
|
||||||
|
|
||||||
# Ask or Call the helper function that asks the user to optionally configure a network.
|
# Ask or Call the helper function that asks the user to optionally configure a network.
|
||||||
if not archinstall.arguments.get('nic', None):
|
if not archinstall.arguments.get('nic', None):
|
||||||
|
|
@ -204,8 +218,6 @@ def ask_user_questions():
|
||||||
|
|
||||||
|
|
||||||
def perform_installation_steps():
|
def perform_installation_steps():
|
||||||
global SIG_TRIGGER
|
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print('This is your chosen configuration:')
|
print('This is your chosen configuration:')
|
||||||
archinstall.log("-- Guided template chosen (with below config) --", level=archinstall.LOG_LEVELS.Debug)
|
archinstall.log("-- Guided template chosen (with below config) --", level=archinstall.LOG_LEVELS.Debug)
|
||||||
|
|
@ -219,120 +231,110 @@ def perform_installation_steps():
|
||||||
We mention the drive one last time, and count from 5 to 0.
|
We mention the drive one last time, and count from 5 to 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
print(f" ! Formatting {archinstall.arguments['harddrive']} in ", end='')
|
if archinstall.arguments.get('harddrive', None):
|
||||||
|
print(f" ! Formatting {archinstall.arguments['harddrive']} in ", end='')
|
||||||
|
archinstall.do_countdown()
|
||||||
|
|
||||||
for i in range(5, 0, -1):
|
"""
|
||||||
print(f"{i}", end='')
|
Setup the blockdevice, filesystem (and optionally encryption).
|
||||||
|
Once that's done, we'll hand over to perform_installation()
|
||||||
for x in range(4):
|
"""
|
||||||
sys.stdout.flush()
|
|
||||||
time.sleep(0.25)
|
|
||||||
print(".", end='')
|
|
||||||
|
|
||||||
if SIG_TRIGGER:
|
|
||||||
abort = input('\nDo you really want to abort (y/n)? ')
|
|
||||||
if abort.strip() != 'n':
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
if SIG_TRIGGER is False:
|
|
||||||
sys.stdin.read()
|
|
||||||
SIG_TRIGGER = False
|
|
||||||
signal.signal(signal.SIGINT, sig_handler)
|
|
||||||
|
|
||||||
# Put back the default/original signal handler now that we're done catching
|
|
||||||
# and interrupting SIGINT with "Do you really want to abort".
|
|
||||||
print()
|
|
||||||
signal.signal(signal.SIGINT, original_sigint_handler)
|
|
||||||
|
|
||||||
"""
|
|
||||||
Setup the blockdevice, filesystem (and optionally encryption).
|
|
||||||
Once that's done, we'll hand over to perform_installation()
|
|
||||||
"""
|
|
||||||
# maybe we can ask the user what they would prefer on uefi systems?
|
|
||||||
if hasUEFI():
|
|
||||||
mode = archinstall.GPT
|
mode = archinstall.GPT
|
||||||
else:
|
if hasUEFI() is False:
|
||||||
mode = archinstall.MBR
|
mode = archinstall.MBR
|
||||||
with archinstall.Filesystem(archinstall.arguments['harddrive'],mode) as fs:
|
|
||||||
# Wipe the entire drive if the disk flag `keep_partitions`is False.
|
|
||||||
if archinstall.arguments['harddrive'].keep_partitions is False:
|
|
||||||
fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'))
|
|
||||||
|
|
||||||
# Check if encryption is desired and mark the root partition as encrypted.
|
with archinstall.Filesystem(archinstall.arguments['harddrive'], mode) as fs:
|
||||||
if archinstall.arguments.get('!encryption-password', None):
|
# Wipe the entire drive if the disk flag `keep_partitions`is False.
|
||||||
root_partition = fs.find_partition('/')
|
if archinstall.arguments['harddrive'].keep_partitions is False:
|
||||||
root_partition.encrypted = True
|
fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'))
|
||||||
|
|
||||||
# After the disk is ready, iterate the partitions and check
|
# Check if encryption is desired and mark the root partition as encrypted.
|
||||||
# which ones are safe to format, and format those.
|
if archinstall.arguments.get('!encryption-password', None):
|
||||||
for partition in archinstall.arguments['harddrive']:
|
root_partition = fs.find_partition('/')
|
||||||
if partition.safe_to_format():
|
root_partition.encrypted = True
|
||||||
# Partition might be marked as encrypted due to the filesystem type crypt_LUKS
|
|
||||||
# But we might have omitted the encryption password question to skip encryption.
|
# After the disk is ready, iterate the partitions and check
|
||||||
# In which case partition.encrypted will be true, but passwd will be false.
|
# which ones are safe to format, and format those.
|
||||||
if partition.encrypted and (passwd := archinstall.arguments.get('!encryption-password', None)):
|
for partition in archinstall.arguments['harddrive']:
|
||||||
partition.encrypt(password=passwd)
|
if partition.safe_to_format():
|
||||||
|
# Partition might be marked as encrypted due to the filesystem type crypt_LUKS
|
||||||
|
# But we might have omitted the encryption password question to skip encryption.
|
||||||
|
# In which case partition.encrypted will be true, but passwd will be false.
|
||||||
|
if partition.encrypted and (passwd := archinstall.arguments.get('!encryption-password', None)):
|
||||||
|
partition.encrypt(password=passwd)
|
||||||
|
else:
|
||||||
|
partition.format()
|
||||||
else:
|
else:
|
||||||
partition.format()
|
archinstall.log(f"Did not format {partition} because .safe_to_format() returned False or .allow_formatting was False.", level=archinstall.LOG_LEVELS.Debug)
|
||||||
|
|
||||||
|
fs.find_partition('/boot').format('vfat')
|
||||||
|
|
||||||
|
if archinstall.arguments.get('!encryption-password', None):
|
||||||
|
# First encrypt and unlock, then format the desired partition inside the encrypted part.
|
||||||
|
# archinstall.luks2() encrypts the partition when entering the with context manager, and
|
||||||
|
# unlocks the drive so that it can be used as a normal block-device within archinstall.
|
||||||
|
with archinstall.luks2(fs.find_partition('/'), 'luksloop', archinstall.arguments.get('!encryption-password', None)) as unlocked_device:
|
||||||
|
unlocked_device.format(fs.find_partition('/').filesystem)
|
||||||
|
unlocked_device.mount('/mnt')
|
||||||
else:
|
else:
|
||||||
archinstall.log(f"Did not format {partition} because .safe_to_format() returned False or .allow_formatting was False.", level=archinstall.LOG_LEVELS.Debug)
|
fs.find_partition('/').format(fs.find_partition('/').filesystem)
|
||||||
|
fs.find_partition('/').mount('/mnt')
|
||||||
|
|
||||||
if archinstall.arguments.get('!encryption-password', None):
|
fs.find_partition('/boot').mount('/mnt/boot')
|
||||||
# First encrypt and unlock, then format the desired partition inside the encrypted part.
|
|
||||||
# archinstall.luks2() encrypts the partition when entering the with context manager, and
|
|
||||||
# unlocks the drive so that it can be used as a normal block-device within archinstall.
|
|
||||||
with archinstall.luks2(fs.find_partition('/'), 'luksloop', archinstall.arguments.get('!encryption-password', None)) as unlocked_device:
|
|
||||||
unlocked_device.format(fs.find_partition('/').filesystem)
|
|
||||||
|
|
||||||
perform_installation(device=unlocked_device,
|
perform_installation('/mnt')
|
||||||
boot_partition=fs.find_partition('/boot'),
|
|
||||||
language=archinstall.arguments['keyboard-language'],
|
|
||||||
mirrors=archinstall.arguments['mirror-region'])
|
|
||||||
else:
|
|
||||||
perform_installation(device=fs.find_partition('/'),
|
|
||||||
boot_partition=fs.find_partition('/boot'),
|
|
||||||
language=archinstall.arguments['keyboard-language'],
|
|
||||||
mirrors=archinstall.arguments['mirror-region'])
|
|
||||||
|
|
||||||
|
|
||||||
def perform_installation(device, boot_partition, language, mirrors):
|
def perform_installation(mountpoint):
|
||||||
"""
|
"""
|
||||||
Performs the installation steps on a block device.
|
Performs the installation steps on a block device.
|
||||||
Only requirement is that the block devices are
|
Only requirement is that the block devices are
|
||||||
formatted and setup prior to entering this function.
|
formatted and setup prior to entering this function.
|
||||||
"""
|
"""
|
||||||
with archinstall.Installer(device, boot_partition=boot_partition, hostname=archinstall.arguments.get('hostname', 'Archinstall')) as installation:
|
with archinstall.Installer(mountpoint) as installation:
|
||||||
## if len(mirrors):
|
## if len(mirrors):
|
||||||
# Certain services might be running that affects the system during installation.
|
# Certain services might be running that affects the system during installation.
|
||||||
# Currently, only one such service is "reflector.service" which updates /etc/pacman.d/mirrorlist
|
# Currently, only one such service is "reflector.service" which updates /etc/pacman.d/mirrorlist
|
||||||
# We need to wait for it before we continue since we opted in to use a custom mirror/region.
|
# We need to wait for it before we continue since we opted in to use a custom mirror/region.
|
||||||
installation.log(f'Waiting for automatic mirror selection has completed before using custom mirrors.')
|
installation.log(f'Waiting for automatic mirror selection (reflector) to complete.', level=archinstall.LOG_LEVELS.Info)
|
||||||
while 'dead' not in (status := archinstall.service_state('reflector')):
|
while archinstall.service_state('reflector') not in ('dead', 'failed'):
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
archinstall.use_mirrors(mirrors) # Set the mirrors for the live medium
|
# Set mirrors used by pacstrap (outside of installation)
|
||||||
if installation.minimal_installation():
|
if archinstall.arguments.get('mirror-region', None):
|
||||||
installation.set_mirrors(mirrors) # Set the mirrors in the installation medium
|
archinstall.use_mirrors(archinstall.arguments['mirror-region']) # Set the mirrors for the live medium
|
||||||
installation.set_keyboard_language(language)
|
|
||||||
if hasUEFI():
|
|
||||||
installation.add_bootloader()
|
|
||||||
else:
|
|
||||||
installation.add_bootloader(bootloder='grub-install')
|
|
||||||
|
|
||||||
|
if installation.minimal_installation():
|
||||||
|
installation.set_hostname(archinstall.arguments['hostname'])
|
||||||
|
if archinstall.arguments['mirror-region'].get("mirrors",{})!= None:
|
||||||
|
installation.set_mirrors(archinstall.arguments['mirror-region']) # Set the mirrors in the installation medium
|
||||||
|
installation.set_keyboard_language(archinstall.arguments['keyboard-language'])
|
||||||
|
installation.add_bootloader()
|
||||||
|
|
||||||
# If user selected to copy the current ISO network configuration
|
# If user selected to copy the current ISO network configuration
|
||||||
# Perform a copy of the config
|
# Perform a copy of the config
|
||||||
if archinstall.arguments.get('nic', None) == 'Copy ISO network configuration to installation':
|
if archinstall.arguments.get('nic', {}) == 'Copy ISO network configuration to installation':
|
||||||
installation.copy_ISO_network_config(enable_services=True) # Sources the ISO network configuration to the install medium.
|
installation.copy_ISO_network_config(enable_services=True) # Sources the ISO network configuration to the install medium.
|
||||||
elif archinstall.arguments.get('nic',{}).get('NetworkManager',False):
|
elif archinstall.arguments.get('nic', {}).get('NetworkManager',False):
|
||||||
installation.add_additional_packages("networkmanager")
|
installation.add_additional_packages("networkmanager")
|
||||||
installation.enable_service('NetworkManager.service')
|
installation.enable_service('NetworkManager.service')
|
||||||
# Otherwise, if a interface was selected, configure that interface
|
# Otherwise, if a interface was selected, configure that interface
|
||||||
elif archinstall.arguments.get('nic', None):
|
elif archinstall.arguments.get('nic', {}):
|
||||||
installation.configure_nic(**archinstall.arguments.get('nic', {}))
|
installation.configure_nic(**archinstall.arguments.get('nic', {}))
|
||||||
installation.enable_service('systemd-networkd')
|
installation.enable_service('systemd-networkd')
|
||||||
installation.enable_service('systemd-resolved')
|
installation.enable_service('systemd-resolved')
|
||||||
|
|
||||||
|
if archinstall.arguments.get('audio', None) != None:
|
||||||
|
installation.log(f"This audio server will be used: {archinstall.arguments.get('audio', None)}", level=archinstall.LOG_LEVELS.Info)
|
||||||
|
if archinstall.arguments.get('audio', None) == 'pipewire':
|
||||||
|
print('Installing pipewire ...')
|
||||||
|
|
||||||
|
installation.add_additional_packages(["pipewire", "pipewire-alsa", "pipewire-jack", "pipewire-media-session", "pipewire-pulse", "gst-plugin-pipewire", "libpulse"])
|
||||||
|
elif archinstall.arguments.get('audio', None) == 'pulseaudio':
|
||||||
|
print('Installing pulseaudio ...')
|
||||||
|
installation.add_additional_packages("pulseaudio")
|
||||||
|
else:
|
||||||
|
installation.log("No audio server will be installed.", level=archinstall.LOG_LEVELS.Info)
|
||||||
|
|
||||||
if archinstall.arguments.get('packages', None) and archinstall.arguments.get('packages', None)[0] != '':
|
if archinstall.arguments.get('packages', None) and archinstall.arguments.get('packages', None)[0] != '':
|
||||||
installation.add_additional_packages(archinstall.arguments.get('packages', None))
|
installation.add_additional_packages(archinstall.arguments.get('packages', None))
|
||||||
|
|
@ -351,6 +353,7 @@ def perform_installation(device, boot_partition, language, mirrors):
|
||||||
|
|
||||||
if (root_pw := archinstall.arguments.get('!root-password', None)) and len(root_pw):
|
if (root_pw := archinstall.arguments.get('!root-password', None)) and len(root_pw):
|
||||||
installation.user_set_pw('root', root_pw)
|
installation.user_set_pw('root', root_pw)
|
||||||
|
|
||||||
if archinstall.arguments['profile'] and archinstall.arguments['profile'].has_post_install():
|
if archinstall.arguments['profile'] and archinstall.arguments['profile'].has_post_install():
|
||||||
with archinstall.arguments['profile'].load_instructions(namespace=f"{archinstall.arguments['profile'].namespace}.py") as imported:
|
with archinstall.arguments['profile'].load_instructions(namespace=f"{archinstall.arguments['profile'].namespace}.py") as imported:
|
||||||
if not imported._post_install():
|
if not imported._post_install():
|
||||||
|
|
@ -360,7 +363,13 @@ def perform_installation(device, boot_partition, language, mirrors):
|
||||||
)
|
)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
installation.log("For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation", fg="yellow")
|
||||||
|
choice = input("Would you like to chroot into the newly created installation and perform post-installation configuration? [Y/n] ")
|
||||||
|
if choice.lower() in ("y", ""):
|
||||||
|
try:
|
||||||
|
installation.drop_to_shell()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
ask_user_questions()
|
ask_user_questions()
|
||||||
perform_installation_steps()
|
perform_installation_steps()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,71 @@
|
||||||
import archinstall, getpass
|
import archinstall
|
||||||
|
|
||||||
# Unmount and close previous runs
|
|
||||||
archinstall.sys_command(f'umount -R /mnt', suppress_errors=True)
|
|
||||||
archinstall.sys_command(f'cryptsetup close /dev/mapper/luksloop', suppress_errors=True)
|
|
||||||
|
|
||||||
# Select a harddrive and a disk password
|
# Select a harddrive and a disk password
|
||||||
harddrive = archinstall.select_disk(archinstall.all_disks())
|
archinstall.log(f"Minimal only supports:")
|
||||||
disk_password = getpass.getpass(prompt='Disk password (won\'t echo): ')
|
archinstall.log(f" * Being installed to a single disk")
|
||||||
|
|
||||||
with archinstall.Filesystem(harddrive) as fs:
|
if archinstall.arguments.get('help', None):
|
||||||
# Use the entire disk instead of setting up partitions on your own
|
archinstall.log(f" - Optional disk encryption via --!encryption-password=<password>")
|
||||||
fs.use_entire_disk('luks2')
|
archinstall.log(f" - Optional filesystem type via --filesystem=<fs type>")
|
||||||
|
archinstall.log(f" - Optional systemd network via --network")
|
||||||
|
|
||||||
if harddrive.partition[1].size == '512M':
|
archinstall.arguments['harddrive'] = archinstall.select_disk(archinstall.all_disks())
|
||||||
raise OSError('Trying to encrypt the boot partition for petes sake..')
|
|
||||||
harddrive.partition[0].format('fat32')
|
|
||||||
|
|
||||||
with archinstall.luks2(harddrive.partition[1], 'luksloop', disk_password) as unlocked_device:
|
def install_on(mountpoint):
|
||||||
unlocked_device.format('btrfs')
|
# We kick off the installer by telling it where the
|
||||||
|
with archinstall.Installer(mountpoint) as installation:
|
||||||
|
# Strap in the base system, add a boot loader and configure
|
||||||
|
# some other minor details as specified by this profile and user.
|
||||||
|
if installation.minimal_installation():
|
||||||
|
installation.set_hostname('minimal-arch')
|
||||||
|
installation.add_bootloader()
|
||||||
|
|
||||||
with archinstall.Installer(unlocked_device, boot_partition=harddrive.partition[0], hostname='testmachine') as installation:
|
# Optionally enable networking:
|
||||||
if installation.minimal_installation():
|
if archinstall.arguments.get('network', None):
|
||||||
installation.add_bootloader()
|
installation.copy_ISO_network_config(enable_services=True)
|
||||||
|
|
||||||
installation.add_additional_packages(['nano', 'wget', 'git'])
|
installation.add_additional_packages(['nano', 'wget', 'git'])
|
||||||
installation.install_profile('awesome')
|
installation.install_profile('minimal')
|
||||||
|
|
||||||
installation.user_create('anton', 'test')
|
installation.user_create('devel', 'devel')
|
||||||
installation.user_set_pw('root', 'toor')
|
installation.user_set_pw('root', 'airoot')
|
||||||
|
|
||||||
|
# Once this is done, we output some useful information to the user
|
||||||
|
# And the installation is complete.
|
||||||
|
archinstall.log(f"There are two new accounts in your installation after reboot:")
|
||||||
|
archinstall.log(f" * root (password: airoot)")
|
||||||
|
archinstall.log(f" * devel (password: devel)")
|
||||||
|
|
||||||
|
if archinstall.arguments['harddrive']:
|
||||||
|
archinstall.arguments['harddrive'].keep_partitions = False
|
||||||
|
|
||||||
|
print(f" ! Formatting {archinstall.arguments['harddrive']} in ", end='')
|
||||||
|
archinstall.do_countdown()
|
||||||
|
|
||||||
|
# First, we configure the basic filesystem layout
|
||||||
|
with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs:
|
||||||
|
# We use the entire disk instead of setting up partitions on your own
|
||||||
|
if archinstall.arguments['harddrive'].keep_partitions is False:
|
||||||
|
fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'))
|
||||||
|
|
||||||
|
boot = fs.find_partition('/boot')
|
||||||
|
root = fs.find_partition('/')
|
||||||
|
|
||||||
|
boot.format('vfat')
|
||||||
|
|
||||||
|
# We encrypt the root partition if we got a password to do so with,
|
||||||
|
# Otherwise we just skip straight to formatting and installation
|
||||||
|
if archinstall.arguments.get('!encryption-password', None):
|
||||||
|
root.encrypted = True
|
||||||
|
root.encrypt(password=archinstall.arguments.get('!encryption-password', None))
|
||||||
|
|
||||||
|
with archinstall.luks2(root, 'luksloop', archinstall.arguments.get('!encryption-password', None)) as unlocked_root:
|
||||||
|
unlocked_root.format(root.filesystem)
|
||||||
|
unlocked_root.mount('/mnt')
|
||||||
|
else:
|
||||||
|
root.format(root.filesystem)
|
||||||
|
root.mount('/mnt')
|
||||||
|
|
||||||
|
boot.mount('/mnt/boot')
|
||||||
|
|
||||||
|
install_on('/mnt')
|
||||||
|
|
@ -6,7 +6,7 @@ installation.install_profile('xorg')
|
||||||
|
|
||||||
installation.add_additional_packages(__packages__)
|
installation.add_additional_packages(__packages__)
|
||||||
|
|
||||||
with open(f'{installation.mountpoint}/etc/X11/xinit/xinitrc', 'r') as xinitrc:
|
with open(f'{installation.target}/etc/X11/xinit/xinitrc', 'r') as xinitrc:
|
||||||
xinitrc_data = xinitrc.read()
|
xinitrc_data = xinitrc.read()
|
||||||
|
|
||||||
for line in xinitrc_data.split('\n'):
|
for line in xinitrc_data.split('\n'):
|
||||||
|
|
@ -20,5 +20,5 @@ for line in xinitrc_data.split('\n'):
|
||||||
xinitrc_data += '\n'
|
xinitrc_data += '\n'
|
||||||
xinitrc_data += 'exec awesome\n'
|
xinitrc_data += 'exec awesome\n'
|
||||||
|
|
||||||
with open(f'{installation.mountpoint}/etc/X11/xinit/xinitrc', 'w') as xinitrc:
|
with open(f'{installation.target}/etc/X11/xinit/xinitrc', 'w') as xinitrc:
|
||||||
xinitrc.write(xinitrc_data)
|
xinitrc.write(xinitrc_data)
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
# "It is recommended also to install the gnome group, which contains applications required for the standard GNOME experience." - Arch Wiki
|
||||||
|
installation.add_additional_packages("budgie-desktop lightdm lightdm-gtk-greeter gnome")
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import archinstall
|
import archinstall
|
||||||
|
|
||||||
installation.add_additional_packages("cinnamon system-config-printer gnome-keyring gnome-terminal blueberry metacity lightdm lightdm-gtk-greeter lightdm-gtk-greeter-settings")
|
installation.add_additional_packages("cinnamon system-config-printer gnome-keyring gnome-terminal blueberry metacity lightdm lightdm-gtk-greeter")
|
||||||
# We'll create a cinnamon-minimal later, but for now, we'll avoid issues by giving more than we need.
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
packages = "deepin deepin-terminal deepin-editor"
|
||||||
|
|
||||||
|
installation.add_additional_packages(packages)
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import archinstall
|
import archinstall
|
||||||
|
|
||||||
installation.add_additional_packages("gnome gnome-extra gdm") # We'll create a gnome-minimal later, but for now, we'll avoid issues by giving more than we need.
|
installation.add_additional_packages("gnome gnome-tweaks gdm")
|
||||||
# Note: gdm should be part of the gnome group, but adding it here for clarity
|
# Note: gdm should be part of the gnome group, but adding it here for clarity
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
import archinstall
|
import archinstall
|
||||||
installation.add_additional_packages("i3lock i3status i3blocks i3-gaps")
|
installation.add_additional_packages("i3-gaps")
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
import archinstall
|
import archinstall
|
||||||
installation.add_additional_packages("i3lock i3status i3blocks i3-wm")
|
installation.add_additional_packages("i3-wm")
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
installation.add_additional_packages("lxqt breeze-icons oxygen-icons xdg-utils ttf-freefont leafpad slock archlinux-wallpaper sddm")
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
installation.add_additional_packages("mate mate-extra lightdm lightdm-gtk-greeter")
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import archinstall
|
||||||
|
__packages__ = "sway swaylock swayidle waybar dmenu light grim slurp pavucontrol alacritty"
|
||||||
|
installation.add_additional_packages(__packages__)
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import archinstall
|
import archinstall
|
||||||
|
__packages__ = "xfce4 xfce4-goodies lightdm lightdm-gtk-greeter"
|
||||||
installation.add_additional_packages("xfce4 xfce4-goodies lightdm lightdm-gtk-greeter lightdm-gtk-greeter-settings")
|
installation.add_additional_packages(__packages__)
|
||||||
# We'll create a xfce4-minimal later, but for now, we'll avoid issues by giving more than we need.
|
|
||||||
|
|
@ -2,7 +2,11 @@
|
||||||
|
|
||||||
import archinstall
|
import archinstall
|
||||||
|
|
||||||
__packages__ = ['nano', 'nemo', 'gpicview-gtk3', 'chromium', 'openssh', 'sshfs', 'htop', 'scrot', 'wget']
|
is_top_level_profile = False
|
||||||
|
|
||||||
|
# New way of defining packages for a profile, which is iterable and can be used out side
|
||||||
|
# of the profile to get a list of "what packages will be installed".
|
||||||
|
__packages__ = ['nemo', 'gpicview-gtk3', 'scrot']
|
||||||
|
|
||||||
def _prep_function(*args, **kwargs):
|
def _prep_function(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -35,13 +39,13 @@ if __name__ == 'awesome':
|
||||||
alacritty.install()
|
alacritty.install()
|
||||||
|
|
||||||
# TODO: Copy a full configuration to ~/.config/awesome/rc.lua instead.
|
# TODO: Copy a full configuration to ~/.config/awesome/rc.lua instead.
|
||||||
with open(f'{installation.mountpoint}/etc/xdg/awesome/rc.lua', 'r') as fh:
|
with open(f'{installation.target}/etc/xdg/awesome/rc.lua', 'r') as fh:
|
||||||
awesome_lua = fh.read()
|
awesome_lua = fh.read()
|
||||||
|
|
||||||
## Replace xterm with alacritty for a smoother experience.
|
## Replace xterm with alacritty for a smoother experience.
|
||||||
awesome_lua = awesome_lua.replace('"xterm"', '"alacritty"')
|
awesome_lua = awesome_lua.replace('"xterm"', '"alacritty"')
|
||||||
|
|
||||||
with open(f'{installation.mountpoint}/etc/xdg/awesome/rc.lua', 'w') as fh:
|
with open(f'{installation.target}/etc/xdg/awesome/rc.lua', 'w') as fh:
|
||||||
fh.write(awesome_lua)
|
fh.write(awesome_lua)
|
||||||
|
|
||||||
## TODO: Configure the right-click-menu to contain the above packages that were installed. (as a user config)
|
## TODO: Configure the right-click-menu to contain the above packages that were installed. (as a user config)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
<<<<<<< HEAD:profiles/cinnamon.py
|
||||||
|
# A desktop environment using "Cinnamon"
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
=======
|
||||||
|
# A desktop environment using "budgie"
|
||||||
|
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
>>>>>>> master:profiles/budgie.py
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
|
def _prep_function(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Magic function called by the importing installer
|
||||||
|
before continuing any further. It also avoids executing any
|
||||||
|
other code in this stage. So it's a safe way to ask the user
|
||||||
|
for more input before any other installer steps start.
|
||||||
|
"""
|
||||||
|
|
||||||
|
<<<<<<< HEAD:profiles/cinnamon.py
|
||||||
|
# Cinnamon requires a functioning Xorg installation.
|
||||||
|
=======
|
||||||
|
# budgie requires a functioning Xorg installation.
|
||||||
|
>>>>>>> master:profiles/budgie.py
|
||||||
|
profile = archinstall.Profile(None, 'xorg')
|
||||||
|
with profile.load_instructions(namespace='xorg.py') as imported:
|
||||||
|
if hasattr(imported, '_prep_function'):
|
||||||
|
return imported._prep_function()
|
||||||
|
else:
|
||||||
|
print('Deprecated (??): xorg profile has no _prep_function() anymore')
|
||||||
|
|
||||||
|
# Ensures that this code only gets executed if executed
|
||||||
|
<<<<<<< HEAD:profiles/cinnamon.py
|
||||||
|
# through importlib.util.spec_from_file_location("cinnamon", "/somewhere/cinnamon.py")
|
||||||
|
# or through conventional import cinnamon
|
||||||
|
if __name__ == 'cinnamon':
|
||||||
|
# Install dependency profiles
|
||||||
|
installation.install_profile('xorg')
|
||||||
|
|
||||||
|
# Install the application cinnamon from the template under /applications/
|
||||||
|
cinnamon = archinstall.Application(installation, 'cinnamon')
|
||||||
|
cinnamon.install()
|
||||||
|
=======
|
||||||
|
# through importlib.util.spec_from_file_location("budgie", "/somewhere/budgie.py")
|
||||||
|
# or through conventional import budgie
|
||||||
|
if __name__ == 'budgie':
|
||||||
|
# Install dependency profiles
|
||||||
|
installation.install_profile('xorg')
|
||||||
|
|
||||||
|
# Install the application budgie from the template under /applications/
|
||||||
|
budgie = archinstall.Application(installation, 'budgie')
|
||||||
|
budgie.install()
|
||||||
|
>>>>>>> master:profiles/budgie.py
|
||||||
|
|
||||||
|
installation.enable_service('lightdm') # Light Display Manager
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
import archinstall
|
import archinstall
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
def _prep_function(*args, **kwargs):
|
def _prep_function(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Magic function called by the importing installer
|
Magic function called by the importing installer
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
# A desktop environment using "Deepin".
|
||||||
|
|
||||||
|
import archinstall, os
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
|
|
||||||
|
def _prep_function(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Magic function called by the importing installer
|
||||||
|
before continuing any further. It also avoids executing any
|
||||||
|
other code in this stage. So it's a safe way to ask the user
|
||||||
|
for more input before any other installer steps start.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Deepin requires a functioning Xorg installation.
|
||||||
|
profile = archinstall.Profile(None, 'xorg')
|
||||||
|
with profile.load_instructions(namespace='xorg.py') as imported:
|
||||||
|
if hasattr(imported, '_prep_function'):
|
||||||
|
return imported._prep_function()
|
||||||
|
else:
|
||||||
|
print('Deprecated (??): xorg profile has no _prep_function() anymore')
|
||||||
|
|
||||||
|
|
||||||
|
# Ensures that this code only gets executed if executed
|
||||||
|
# through importlib.util.spec_from_file_location("deepin", "/somewhere/deepin.py")
|
||||||
|
# or through conventional import deepin
|
||||||
|
if __name__ == 'deepin':
|
||||||
|
# Install dependency profiles
|
||||||
|
installation.install_profile('xorg')
|
||||||
|
|
||||||
|
# Install the application deepin from the template under /applications/
|
||||||
|
deepin = archinstall.Application(installation, 'deepin')
|
||||||
|
deepin.install()
|
||||||
|
|
||||||
|
# Enable autostart of Deepin for all users
|
||||||
|
installation.enable_service('lightdm')
|
||||||
|
|
@ -2,6 +2,12 @@
|
||||||
|
|
||||||
import archinstall, os
|
import archinstall, os
|
||||||
|
|
||||||
|
is_top_level_profile = True
|
||||||
|
|
||||||
|
# New way of defining packages for a profile, which is iterable and can be used out side
|
||||||
|
# of the profile to get a list of "what packages will be installed".
|
||||||
|
__packages__ = ['nano', 'vim', 'openssh', 'htop', 'wget', 'iwd', 'wireless_tools', 'wpa_supplicant', 'smartmontools', 'xdg-utils']
|
||||||
|
|
||||||
def _prep_function(*args, **kwargs):
|
def _prep_function(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Magic function called by the importing installer
|
Magic function called by the importing installer
|
||||||
|
|
@ -10,11 +16,11 @@ def _prep_function(*args, **kwargs):
|
||||||
for more input before any other installer steps start.
|
for more input before any other installer steps start.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
supported_desktops = ['gnome', 'kde', 'awesome', 'xfce4', 'cinnamon']
|
supported_desktops = ['gnome', 'kde', 'awesome', 'sway', 'cinnamon', 'xfce4', 'lxqt', 'i3', 'budgie', 'mate', 'deepin']
|
||||||
desktop = archinstall.generic_select(supported_desktops, 'Select your desired desktop environment: ')
|
desktop = archinstall.generic_select(supported_desktops, 'Select your desired desktop environment: ')
|
||||||
|
|
||||||
# Temporarly store the selected desktop profile
|
# Temporarily store the selected desktop profile
|
||||||
# in a session-safe location, since this module will get re-loaded
|
# in a session-safe location, since this module will get reloaded
|
||||||
# the next time it gets executed.
|
# the next time it gets executed.
|
||||||
archinstall.storage['_desktop_profile'] = desktop
|
archinstall.storage['_desktop_profile'] = desktop
|
||||||
|
|
||||||
|
|
@ -29,7 +35,7 @@ def _prep_function(*args, **kwargs):
|
||||||
if __name__ == 'desktop':
|
if __name__ == 'desktop':
|
||||||
"""
|
"""
|
||||||
This "profile" is a meta-profile.
|
This "profile" is a meta-profile.
|
||||||
There are no specific desktop-steps, it simply routes
|
There are no desktop-specific steps, it simply routes
|
||||||
the installer to whichever desktop environment/window manager was chosen.
|
the installer to whichever desktop environment/window manager was chosen.
|
||||||
|
|
||||||
Maybe in the future, a network manager or similar things *could* be added here.
|
Maybe in the future, a network manager or similar things *could* be added here.
|
||||||
|
|
@ -37,9 +43,13 @@ if __name__ == 'desktop':
|
||||||
it trying to be a turn-key desktop distribution.
|
it trying to be a turn-key desktop distribution.
|
||||||
|
|
||||||
There are plenty of desktop-turn-key-solutions based on Arch Linux,
|
There are plenty of desktop-turn-key-solutions based on Arch Linux,
|
||||||
this is therefor just a helper to get started
|
this is therefore just a helper to get started
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Install common packages for all desktop environments
|
||||||
|
installation.add_additional_packages(__packages__)
|
||||||
|
|
||||||
# TODO: Remove magic variable 'installation' and place it
|
# TODO: Remove magic variable 'installation' and place it
|
||||||
# in archinstall.storage or archinstall.session/archinstall.installation
|
# in archinstall.storage or archinstall.session/archinstall.installation
|
||||||
installation.install_profile(archinstall.storage['_desktop_profile'])
|
installation.install_profile(archinstall.storage['_desktop_profile'])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
import archinstall
|
import archinstall
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
def _prep_function(*args, **kwargs):
|
def _prep_function(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Magic function called by the importing installer
|
Magic function called by the importing installer
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import archinstall, subprocess
|
import archinstall, subprocess
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
def _prep_function(*args, **kwargs):
|
def _prep_function(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Magic function called by the importing installer
|
Magic function called by the importing installer
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import archinstall, subprocess
|
import archinstall, subprocess
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
def _prep_function(*args, **kwargs):
|
def _prep_function(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Magic function called by the importing installer
|
Magic function called by the importing installer
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
# Common package for i3, lets user select which i3 configuration they want.
|
||||||
|
|
||||||
|
import archinstall, os
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
|
# New way of defining packages for a profile, which is iterable and can be used out side
|
||||||
|
# of the profile to get a list of "what packages will be installed".
|
||||||
|
__packages__ = ['i3lock', 'i3status', 'i3blocks', 'xterm']
|
||||||
|
|
||||||
|
def _prep_function(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Magic function called by the importing installer
|
||||||
|
before continuing any further. It also avoids executing any
|
||||||
|
other code in this stage. So it's a safe way to ask the user
|
||||||
|
for more input before any other installer steps start.
|
||||||
|
"""
|
||||||
|
|
||||||
|
supported_configurations = ['i3-wm', 'i3-gaps']
|
||||||
|
desktop = archinstall.generic_select(supported_configurations, 'Select your desired configuration: ')
|
||||||
|
|
||||||
|
# Temporarily store the selected desktop profile
|
||||||
|
# in a session-safe location, since this module will get reloaded
|
||||||
|
# the next time it gets executed.
|
||||||
|
archinstall.storage['_i3_configuration'] = desktop
|
||||||
|
|
||||||
|
# i3 requires a functioning Xorg installation.
|
||||||
|
profile = archinstall.Profile(None, 'xorg')
|
||||||
|
with profile.load_instructions(namespace='xorg.py') as imported:
|
||||||
|
if hasattr(imported, '_prep_function'):
|
||||||
|
return imported._prep_function()
|
||||||
|
else:
|
||||||
|
print('Deprecated (??): xorg profile has no _prep_function() anymore')
|
||||||
|
|
||||||
|
if __name__ == 'i3':
|
||||||
|
"""
|
||||||
|
This "profile" is a meta-profile.
|
||||||
|
There are no desktop-specific steps, it simply routes
|
||||||
|
the installer to whichever desktop environment/window manager was chosen.
|
||||||
|
|
||||||
|
Maybe in the future, a network manager or similar things *could* be added here.
|
||||||
|
We should honor that Arch Linux does not officially endorse a desktop-setup, nor is
|
||||||
|
it trying to be a turn-key desktop distribution.
|
||||||
|
|
||||||
|
There are plenty of desktop-turn-key-solutions based on Arch Linux,
|
||||||
|
this is therefore just a helper to get started
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Install common packages for all i3 configurations
|
||||||
|
installation.add_additional_packages(__packages__)
|
||||||
|
|
||||||
|
# Install dependency profiles
|
||||||
|
installation.install_profile('xorg')
|
||||||
|
|
||||||
|
# gaps is installed by deafult so we are overriding it here
|
||||||
|
installation.add_additional_packages("lightdm-gtk-greeter lightdm")
|
||||||
|
|
||||||
|
# Auto start lightdm for all users
|
||||||
|
installation.enable_service('lightdm')
|
||||||
|
|
||||||
|
# install the i3 group now
|
||||||
|
i3 = archinstall.Application(installation, archinstall.storage['_i3_configuration'])
|
||||||
|
i3.install()
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
# A desktop environement using "KDE".
|
# A desktop environment using "KDE".
|
||||||
|
|
||||||
import archinstall, os
|
import archinstall, os
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
# TODO: Remove hard dependency of bash (due to .bash_profile)
|
# TODO: Remove hard dependency of bash (due to .bash_profile)
|
||||||
|
|
||||||
def _prep_function(*args, **kwargs):
|
def _prep_function(*args, **kwargs):
|
||||||
|
|
@ -20,14 +22,14 @@ def _prep_function(*args, **kwargs):
|
||||||
else:
|
else:
|
||||||
print('Deprecated (??): xorg profile has no _prep_function() anymore')
|
print('Deprecated (??): xorg profile has no _prep_function() anymore')
|
||||||
|
|
||||||
|
"""
|
||||||
def _post_install(*args, **kwargs):
|
def _post_install(*args, **kwargs):
|
||||||
if "nvidia" in _gfx_driver_packages:
|
if "nvidia" in _gfx_driver_packages:
|
||||||
print("Plasma Wayland has known compatibility issues with the proprietary Nvidia driver")
|
print("Plasma Wayland has known compatibility issues with the proprietary Nvidia driver")
|
||||||
choice = input("Would you like plasma-wayland to be the default session [Y/n] ").lower()
|
print("After booting, you can choose between Wayland and Xorg using the drop-down menu")
|
||||||
if choice == "y":
|
|
||||||
installation.arch_chroot("mv /usr/share/xsessions/plasma.desktop /usr/share/xsessions/plasmax11.desktop")
|
|
||||||
installation.arch_chroot("mv /usr/share/wayland-sessions/plasmawayland.desktop /usr/share/wayland-sessions/plasma.desktop")
|
|
||||||
return True
|
return True
|
||||||
|
"""
|
||||||
|
|
||||||
# Ensures that this code only gets executed if executed
|
# Ensures that this code only gets executed if executed
|
||||||
# through importlib.util.spec_from_file_location("kde", "/somewhere/kde.py")
|
# through importlib.util.spec_from_file_location("kde", "/somewhere/kde.py")
|
||||||
# or through conventional import kde
|
# or through conventional import kde
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
|
||||||
|
# A desktop environment using "LXQt"
|
||||||
|
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
|
def _prep_function(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Magic function called by the importing installer
|
||||||
|
before continuing any further. It also avoids executing any
|
||||||
|
other code in this stage. So it's a safe way to ask the user
|
||||||
|
for more input before any other installer steps start.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# LXQt requires a functional xorg installation.
|
||||||
|
profile = archinstall.Profile(None, 'xorg')
|
||||||
|
with profile.load_instructions(namespace='xorg.py') as imported:
|
||||||
|
if hasattr(imported, '_prep_function'):
|
||||||
|
return imported._prep_function()
|
||||||
|
else:
|
||||||
|
print('Deprecated (??): xorg profile has no _prep_function() anymore')
|
||||||
|
|
||||||
|
# Ensures that this code only gets executed if executed
|
||||||
|
# through importlib.util.spec_from_file_location("lxqt", "/somewhere/lxqt.py")
|
||||||
|
# or through conventional import lxqt
|
||||||
|
if __name__ == 'lxqt':
|
||||||
|
# Install dependency profiles
|
||||||
|
installation.install_profile('xorg')
|
||||||
|
|
||||||
|
# Install the application xfce4 from the template under /applications/
|
||||||
|
xfce = archinstall.Application(installation, 'lxqt')
|
||||||
|
xfce.install()
|
||||||
|
|
||||||
|
installation.enable_service('sddm') # SDDM Display Manager
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
# A desktop environment using "MATE"
|
||||||
|
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
|
def _prep_function(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Magic function called by the importing installer
|
||||||
|
before continuing any further. It also avoids executing any
|
||||||
|
other code in this stage. So it's a safe way to ask the user
|
||||||
|
for more input before any other installer steps start.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# MATE requires a functional xorg installation.
|
||||||
|
profile = archinstall.Profile(None, 'xorg')
|
||||||
|
with profile.load_instructions(namespace='xorg.py') as imported:
|
||||||
|
if hasattr(imported, '_prep_function'):
|
||||||
|
return imported._prep_function()
|
||||||
|
else:
|
||||||
|
print('Deprecated (??): xorg profile has no _prep_function() anymore')
|
||||||
|
|
||||||
|
# Ensures that this code only gets executed if executed
|
||||||
|
# through importlib.util.spec_from_file_location("mate", "/somewhere/mate.py")
|
||||||
|
# or through conventional import mate
|
||||||
|
if __name__ == 'mate':
|
||||||
|
# Install dependency profiles
|
||||||
|
installation.install_profile('xorg')
|
||||||
|
|
||||||
|
# Install the application mate from the template under /applications/
|
||||||
|
mate = archinstall.Application(installation, 'mate')
|
||||||
|
mate.install()
|
||||||
|
|
||||||
|
installation.enable_service('lightdm') # Light Display Manager
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Used to do a minimal install
|
||||||
|
|
||||||
|
import archinstall, os
|
||||||
|
|
||||||
|
is_top_level_profile = True
|
||||||
|
|
||||||
|
def _prep_function(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Magic function called by the importing installer
|
||||||
|
before continuing any further. For minimal install,
|
||||||
|
we don't need to do anything special here, but it
|
||||||
|
needs to exist and return True.
|
||||||
|
"""
|
||||||
|
return True # Do nothing and just return True
|
||||||
|
|
||||||
|
if __name__ == 'minimal':
|
||||||
|
"""
|
||||||
|
This "profile" is a meta-profile.
|
||||||
|
It is used for a custom minimal installation, without any desktop-specific packages.
|
||||||
|
"""
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
# A desktop environment using "Sway"
|
||||||
|
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
|
def _prep_function(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Magic function called by the importing installer
|
||||||
|
before continuing any further. It also avoids executing any
|
||||||
|
other code in this stage. So it's a safe way to ask the user
|
||||||
|
for more input before any other installer steps start.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Ensures that this code only gets executed if executed
|
||||||
|
# through importlib.util.spec_from_file_location("sway", "/somewhere/sway.py")
|
||||||
|
# or through conventional import sway
|
||||||
|
if __name__ == 'sway':
|
||||||
|
# Install the application sway from the template under /applications/
|
||||||
|
sway = archinstall.Application(installation, 'sway')
|
||||||
|
sway.install()
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
import archinstall
|
import archinstall
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
def _prep_function(*args, **kwargs):
|
def _prep_function(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Magic function called by the importing installer
|
Magic function called by the importing installer
|
||||||
|
|
|
||||||
|
|
@ -2,88 +2,7 @@
|
||||||
|
|
||||||
import archinstall, os
|
import archinstall, os
|
||||||
|
|
||||||
AVAILABLE_DRIVERS = {
|
is_top_level_profile = True
|
||||||
# Sub-dicts are layer-2 options to be selected
|
|
||||||
# and lists are a list of packages to be installed
|
|
||||||
'AMD / ATI' : {
|
|
||||||
'amd' : ['xf86-video-amdgpu'],
|
|
||||||
'ati' : ['xf86-video-ati']
|
|
||||||
},
|
|
||||||
'intel' : ['xf86-video-intel'],
|
|
||||||
'nvidia' : {
|
|
||||||
'open source' : ['xf86-video-nouveau'],
|
|
||||||
'proprietary' : ['nvidia']
|
|
||||||
},
|
|
||||||
'mesa' : ['mesa'],
|
|
||||||
'fbdev' : ['xf86-video-fbdev'],
|
|
||||||
'vesa' : ['xf86-video-vesa'],
|
|
||||||
'vmware' : ['xf86-video-vmware']
|
|
||||||
}
|
|
||||||
|
|
||||||
def select_driver(options):
|
|
||||||
"""
|
|
||||||
Some what convoluted function, which's job is simple.
|
|
||||||
Select a graphics driver from a pre-defined set of popular options.
|
|
||||||
|
|
||||||
(The template xorg is for beginner users, not advanced, and should
|
|
||||||
there for appeal to the general public first and edge cases later)
|
|
||||||
"""
|
|
||||||
drivers = sorted(list(options))
|
|
||||||
|
|
||||||
if len(drivers) >= 1:
|
|
||||||
for index, driver in enumerate(drivers):
|
|
||||||
print(f"{index}: {driver}")
|
|
||||||
|
|
||||||
print(' -- The above list are supported graphic card drivers. --')
|
|
||||||
print(' -- You need to select (and read about) which one you need. --')
|
|
||||||
|
|
||||||
lspci = archinstall.sys_command(f'/usr/bin/lspci')
|
|
||||||
for line in lspci.trace_log.split(b'\r\n'):
|
|
||||||
if b' vga ' in line.lower():
|
|
||||||
if b'nvidia' in line.lower():
|
|
||||||
print(' ** nvidia card detected, suggested driver: nvidia **')
|
|
||||||
elif b'amd' in line.lower():
|
|
||||||
print(' ** AMD card detected, suggested driver: AMD / ATI **')
|
|
||||||
|
|
||||||
selected_driver = input('Select your graphics card driver: ')
|
|
||||||
initial_option = selected_driver
|
|
||||||
|
|
||||||
# Disabled search for now, only a few profiles exist anyway
|
|
||||||
#
|
|
||||||
#print(' -- You can enter ? or help to search for more drivers --')
|
|
||||||
#if selected_driver.lower() in ('?', 'help'):
|
|
||||||
# filter_string = input('Search for layout containing (example: "sv-"): ')
|
|
||||||
# new_options = search_keyboard_layout(filter_string)
|
|
||||||
# return select_language(new_options)
|
|
||||||
if selected_driver.isdigit() and (pos := int(selected_driver)) <= len(drivers)-1:
|
|
||||||
selected_driver = options[drivers[pos]]
|
|
||||||
elif selected_driver in options:
|
|
||||||
selected_driver = options[options.index(selected_driver)]
|
|
||||||
elif len(selected_driver) == 0:
|
|
||||||
raise archinstall.RequirementError("At least one graphics driver is needed to support a graphical environment. Please restart the installer and try again.")
|
|
||||||
else:
|
|
||||||
raise archinstall.RequirementError("Selected driver does not exist.")
|
|
||||||
|
|
||||||
if type(selected_driver) == dict:
|
|
||||||
driver_options = sorted(list(selected_driver))
|
|
||||||
for index, driver_package_group in enumerate(driver_options):
|
|
||||||
print(f"{index}: {driver_package_group}")
|
|
||||||
|
|
||||||
selected_driver_package_group = input(f'Which driver-type do you want for {initial_option}: ')
|
|
||||||
if selected_driver_package_group.isdigit() and (pos := int(selected_driver_package_group)) <= len(driver_options)-1:
|
|
||||||
selected_driver_package_group = selected_driver[driver_options[pos]]
|
|
||||||
elif selected_driver_package_group in selected_driver:
|
|
||||||
selected_driver_package_group = selected_driver[selected_driver.index(selected_driver_package_group)]
|
|
||||||
elif len(selected_driver_package_group) == 0:
|
|
||||||
raise archinstall.RequirementError(f"At least one driver package is required for a graphical environment using {selected_driver}. Please restart the installer and try again.")
|
|
||||||
else:
|
|
||||||
raise archinstall.RequirementError(f"Selected driver-type does not exist for {initial_option}.")
|
|
||||||
|
|
||||||
return selected_driver_package_group
|
|
||||||
|
|
||||||
return selected_driver
|
|
||||||
|
|
||||||
raise archinstall.RequirementError("Selecting drivers require a least one profile to be given as an option.")
|
|
||||||
|
|
||||||
def _prep_function(*args, **kwargs):
|
def _prep_function(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -92,10 +11,8 @@ def _prep_function(*args, **kwargs):
|
||||||
other code in this stage. So it's a safe way to ask the user
|
other code in this stage. So it's a safe way to ask the user
|
||||||
for more input before any other installer steps start.
|
for more input before any other installer steps start.
|
||||||
"""
|
"""
|
||||||
print('You need to select which graphics card you\'re using.')
|
|
||||||
print('This in order to setup the required graphics drivers.')
|
|
||||||
|
|
||||||
__builtins__['_gfx_driver_packages'] = select_driver(AVAILABLE_DRIVERS)
|
__builtins__['_gfx_driver_packages'] = archinstall.select_driver()
|
||||||
|
|
||||||
# TODO: Add language section and/or merge it with the locale selected
|
# TODO: Add language section and/or merge it with the locale selected
|
||||||
# earlier in for instance guided.py installer.
|
# earlier in for instance guided.py installer.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["flit_core >=2,<4"]
|
||||||
|
build-backend = "flit_core.buildapi"
|
||||||
|
|
||||||
|
[tool.flit.metadata]
|
||||||
|
module = "archinstall"
|
||||||
|
author = "Anton Hvornum"
|
||||||
|
author-email = "anton@hvornum.se"
|
||||||
|
home-page = "https://archlinux.org"
|
||||||
|
classifiers = [ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||||
|
"Operating System :: POSIX :: Linux",
|
||||||
|
]
|
||||||
|
description-file = "README.md"
|
||||||
|
requires-python=">=3.8"
|
||||||
|
[tool.flit.metadata.urls]
|
||||||
|
Source = "https://github.com/archlinux/archinstall"
|
||||||
|
Documentation = "https://archinstall.readthedocs.io/"
|
||||||
|
|
||||||
|
[tool.flit.scripts]
|
||||||
|
archinstall = "archinstall:run_as_a_module"
|
||||||
|
|
||||||
|
[tool.flit.sdist]
|
||||||
|
include = ["docs/"]
|
||||||
|
exclude = ["docs/*.html", "docs/_static","docs/*.png","docs/*.psd"]
|
||||||
|
|
||||||
|
[tool.flit.metadata.requires-extra]
|
||||||
|
doc = ["sphinx"]
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
[metadata]
|
||||||
|
name = archinstall
|
||||||
|
version = attr: archinstall.__version__
|
||||||
|
description = Arch Linux installer - guided, templates etc.
|
||||||
|
author = Anton Hvornum
|
||||||
|
author_email = anton@hvornum.se
|
||||||
|
long_description = file: README.md
|
||||||
|
long_description_content_type = text/markdown
|
||||||
|
license = GPL
|
||||||
|
license_files =
|
||||||
|
LICENSE
|
||||||
|
project_urls =
|
||||||
|
Source = https://github.com/archlinux/archinstall
|
||||||
|
Documentation = https://archinstall.readthedocs.io/
|
||||||
|
classifers =
|
||||||
|
Programming Language :: Python :: 3
|
||||||
|
Programming Language :: Python :: 3.8
|
||||||
|
Programming Language :: Python :: 3.9
|
||||||
|
License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
||||||
|
Operating System :: POSIX :: Linux
|
||||||
|
|
||||||
|
[options]
|
||||||
|
packages = find:
|
||||||
|
python_requires = >= 3.8
|
||||||
|
|
||||||
|
[options.packages.find]
|
||||||
|
include =
|
||||||
|
archinstall
|
||||||
|
archinstall.*
|
||||||
|
|
||||||
|
[options.package_data]
|
||||||
|
archinstall =
|
||||||
|
examples/*.py
|
||||||
|
profiles/*.py
|
||||||
|
profiles/applications/*.py
|
||||||
|
|
||||||
|
[options.entry_points]
|
||||||
|
console_scripts =
|
||||||
|
archinstall = archinstall:run_as_a_module
|
||||||
29
setup.py
29
setup.py
|
|
@ -1,27 +1,2 @@
|
||||||
import setuptools, glob, shutil
|
import setuptools
|
||||||
|
setuptools.setup()
|
||||||
with open("README.md", "r") as fh:
|
|
||||||
long_description = fh.read()
|
|
||||||
|
|
||||||
with open('VERSION', 'r') as fh:
|
|
||||||
VERSION = fh.read()
|
|
||||||
|
|
||||||
setuptools.setup(
|
|
||||||
name="archinstall",
|
|
||||||
version=VERSION,
|
|
||||||
author="Anton Hvornum",
|
|
||||||
author_email="anton@hvornum.se",
|
|
||||||
description="Arch Linux installer - guided, templates etc.",
|
|
||||||
long_description=long_description,
|
|
||||||
long_description_content_type="text/markdown",
|
|
||||||
url="https://github.com/Torxed/archinstall",
|
|
||||||
packages=setuptools.find_packages(),
|
|
||||||
classifiers=[
|
|
||||||
"Programming Language :: Python :: 3.8",
|
|
||||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
|
||||||
"Operating System :: POSIX :: Linux",
|
|
||||||
],
|
|
||||||
python_requires='>=3.8',
|
|
||||||
setup_requires=['wheel'],
|
|
||||||
package_data={'archinstall': glob.glob('examples/*.py') + glob.glob('profiles/*.py') + glob.glob('profiles/applications/*.py')},
|
|
||||||
)
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue