Merge branch 'master' of https://github.com/archlinux/archinstall into torxed-partitioning
This commit is contained in:
commit
ea0d0a8817
|
|
@ -3,17 +3,16 @@
|
|||
name: Build Arch ISO with ArchInstall Commit
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main # In case we adopt this convention in the future
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
- 'docs/**'
|
||||
- '**.editorconfig'
|
||||
- '**.gitignore'
|
||||
- '**.yml'
|
||||
- '**.yaml'
|
||||
- '**.md'
|
||||
- '**.toml'
|
||||
- '**.cfg'
|
||||
- 'LICENSE'
|
||||
- 'PKGBUILD'
|
||||
|
||||
|
|
@ -29,7 +28,11 @@ jobs:
|
|||
- 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: echo "pip uninstall archinstall -y; cd archinstall-git; python setup.py install" > /tmp/archlive/airootfs/root/.zprofile
|
||||
- run: echo "echo \"This is an unofficial ISO for development and testing of archinstall. No support will be provided.\"" >> /tmp/archlive/airootfs/root/.zprofile
|
||||
- run: echo "echo \"This ISO was built from Git SHA $GITHUB_SHA\"" >> /tmp/archlive/airootfs/root/.zprofile
|
||||
- run: echo "echo \"Type archinstall to launch the installer.\"" >> /tmp/archlive/airootfs/root/.zprofile
|
||||
- run: cat /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
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ name: Upload archinstall to PyPi
|
|||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
types: [created, published]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
|
@ -21,11 +21,10 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install setuptools wheel twine
|
||||
pip install setuptools wheel flit
|
||||
- name: Build and publish
|
||||
env:
|
||||
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||
FLIT_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
||||
FLIT_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||
run: |
|
||||
python setup.py sdist bdist_wheel
|
||||
twine upload dist/*
|
||||
flit publish
|
||||
|
|
|
|||
|
|
@ -21,3 +21,6 @@ SAFETY_LOCK
|
|||
**/**.target
|
||||
**/**.qcow2
|
||||
**/test.py
|
||||
**/archiso
|
||||
/guided.py
|
||||
/install.log
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
[distutils]
|
||||
index-servers =
|
||||
pypi
|
||||
|
||||
[pypi]
|
||||
repository = https://upload.pypi.org/legacy/
|
||||
|
|
@ -1,9 +1,15 @@
|
|||
# Contributing to archinstall
|
||||
|
||||
Any contributions through pull requests are welcome as this project aims to be a community based project to ease some Arch Linux installation steps.<br>
|
||||
Bear in mind that the future of this repo might be transferred to the official [GitLab repo under Arch Linux](http://gitlab.archlinux.org/archlinux/) *(if they so choose to adopt the project)*.
|
||||
Any contributions through pull requests are welcome as this project aims to be a community based project to ease some Arch Linux installation steps. Bear in mind that in the future this repo might be transferred to the official [GitLab repo under Arch Linux](http://gitlab.archlinux.org/archlinux/) *(if GitLab becomes open to the general public)*.
|
||||
|
||||
Therefore guidelines and style changes to the code might come into affect as well as guidelines surrounding bug reporting and discussions.
|
||||
|
||||
## Branches
|
||||
|
||||
`master` is currently the default branch, and that's where all future feature work is being done, this means that `master` is a living entity and will most likely never be in a fully stable state. For stable releases, please see the tagged commits.
|
||||
|
||||
Patch releases will be done against their own branches, branched from stable tagged releases and will be named according to the version it will become on release *(Patches to `v2.1.4` will be done on branch `v2.1.5` for instance)*.
|
||||
|
||||
## Discussions
|
||||
|
||||
Currently, questions, bugs and suggestions should be reported through [GitHub issue tracker](https://github.com/archlinux/archinstall/issues).<br>
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -26,6 +26,11 @@ Assuming you are on a Arch Linux live-ISO and booted into EFI mode.
|
|||
|
||||
# python -m archinstall guided
|
||||
|
||||
# Help?
|
||||
|
||||
Submit an issue here on Github, or submit a post in the discord help channel.<br>
|
||||
When doing so, attach the `/var/log/archinstall/install.log` to the issue ticket. This helps us help you!
|
||||
|
||||
# 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.
|
||||
|
|
@ -95,19 +100,18 @@ with archinstall.Installer('/mnt') as installation:
|
|||
This installer will perform the following:
|
||||
|
||||
* Prompt the user to select a disk and disk-password
|
||||
* Proceed to wipe the selected disk with a `GPT` partition table.
|
||||
* Proceed to wipe the selected disk with a `GPT` partition table on a UEFI system and MBR on a bios system.
|
||||
* Sets up a default 100% used disk with encryption.
|
||||
* Installs a basic instance of Arch Linux *(base base-devel linux linux-firmware btrfs-progs efibootmgr)*
|
||||
* Installs and configures a bootloader 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)*
|
||||
* 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.
|
||||
|
||||
# Help
|
||||
## Unattended installation based on MAC address
|
||||
|
||||
Submit an issue on Github, or submit a post in the discord help channel.<br>
|
||||
When doing so, attach any `install-session_*.log` to the issue ticket which can be found under `~/.cache/archinstall/`.
|
||||
Archinstall comes with a [unattended](examples/unattended.py) example which will look for a matching profile for the machine it is being run on, based on any local MAC address. For instance, if the machine that [unattended](examples/unattended.py) is run on has the MAC address `52:54:00:12:34:56` it will look for a profile called [profiles/52-54-00-12-34-56.py](profiles/52-54-00-12-34-56.py). If it's found, the unattended installation will commence and source that profile as it's installation proceedure.
|
||||
|
||||
# Testing
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
"""Arch Linux installer - guided, templates etc."""
|
||||
from .lib.general import *
|
||||
from .lib.disk import *
|
||||
from .lib.user_interaction import *
|
||||
from .lib.exceptions import *
|
||||
from .lib.installer import *
|
||||
from .lib.installer import __packages__, Installer
|
||||
from .lib.profiles import *
|
||||
from .lib.luks import *
|
||||
from .lib.mirrors import *
|
||||
|
|
@ -14,7 +15,7 @@ from .lib.output import *
|
|||
from .lib.storage import *
|
||||
from .lib.hardware import *
|
||||
|
||||
__version__ = "2.1.4"
|
||||
__version__ = "2.2.0"
|
||||
|
||||
## Basic version of arg.parse() supporting:
|
||||
## --key=value
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import glob, re, os, json, time, hashlib
|
||||
import pathlib, traceback
|
||||
import pathlib, traceback, logging
|
||||
from collections import OrderedDict
|
||||
from .exceptions import DiskError
|
||||
from .general import *
|
||||
from .output import log, LOG_LEVELS
|
||||
from .output import log
|
||||
from .storage import storage
|
||||
from .hardware import hasUEFI
|
||||
|
||||
ROOT_DIR_PATTERN = re.compile('^.*?/devices')
|
||||
GPT = 0b00000001
|
||||
|
|
@ -73,7 +74,7 @@ class BlockDevice():
|
|||
raise DiskError(f'Could not locate backplane info for "{self.path}"')
|
||||
|
||||
if self.info['type'] == 'loop':
|
||||
for drive in json.loads(b''.join(sys_command(f'losetup --json', hide_from_log=True)).decode('UTF_8'))['loopdevices']:
|
||||
for drive in json.loads(b''.join(sys_command(['losetup', '--json'], hide_from_log=True)).decode('UTF_8'))['loopdevices']:
|
||||
if not drive['name'] == self.path: continue
|
||||
|
||||
return drive['back-file']
|
||||
|
|
@ -87,17 +88,17 @@ class BlockDevice():
|
|||
raise DiskError(f'A crypt device ({self.path}) without a parent kernel device name.')
|
||||
return f"/dev/{self.info['pkname']}"
|
||||
else:
|
||||
log(f"Unknown blockdevice type for {self.path}: {self.info['type']}", level=LOG_LEVELS.Debug)
|
||||
log(f"Unknown blockdevice type for {self.path}: {self.info['type']}", level=logging.DEBUG)
|
||||
|
||||
# if not stat.S_ISBLK(os.stat(full_path).st_mode):
|
||||
# raise DiskError(f'Selected disk "{full_path}" is not a block device.')
|
||||
|
||||
@property
|
||||
def partitions(self):
|
||||
o = b''.join(sys_command(f'partprobe {self.path}'))
|
||||
o = b''.join(sys_command(['partprobe', self.path]))
|
||||
|
||||
#o = b''.join(sys_command('/usr/bin/lsblk -o name -J -b {dev}'.format(dev=dev)))
|
||||
o = b''.join(sys_command(f'/usr/bin/lsblk -J {self.path}'))
|
||||
o = b''.join(sys_command(['/usr/bin/lsblk', '-J', self.path]))
|
||||
|
||||
if b'not a block device' in o:
|
||||
raise DiskError(f'Can not read partitions off something that isn\'t a block device: {self.path}')
|
||||
|
|
@ -128,7 +129,7 @@ class BlockDevice():
|
|||
|
||||
@property
|
||||
def uuid(self):
|
||||
log(f'BlockDevice().uuid is untested!', level=LOG_LEVELS.Warning, fg='yellow')
|
||||
log(f'BlockDevice().uuid is untested!', level=logging.WARNING, fg='yellow')
|
||||
"""
|
||||
Returns the disk UUID as returned by lsblk.
|
||||
This is more reliable than relying on /dev/disk/by-partuuid as
|
||||
|
|
@ -220,9 +221,6 @@ class Partition():
|
|||
|
||||
@encrypted.setter
|
||||
def encrypted(self, value :bool):
|
||||
if value:
|
||||
log(f'Marking {self} as encrypted: {value}', level=LOG_LEVELS.Debug)
|
||||
#log(f"Callstrack when marking the partition: {''.join(traceback.format_stack())}", level=LOG_LEVELS.Debug)
|
||||
|
||||
self._encrypted = value
|
||||
|
||||
|
|
@ -239,7 +237,7 @@ class Partition():
|
|||
return self.path
|
||||
|
||||
def detect_inner_filesystem(self, password):
|
||||
log(f'Trying to detect inner filesystem format on {self} (This might take a while)', level=LOG_LEVELS.Info)
|
||||
log(f'Trying to detect inner filesystem format on {self} (This might take a while)', level=logging.INFO)
|
||||
from .luks import luks2
|
||||
|
||||
try:
|
||||
|
|
@ -268,16 +266,16 @@ class Partition():
|
|||
|
||||
def safe_to_format(self):
|
||||
if self.allow_formatting is False:
|
||||
log(f"Partition {self} is not marked for formatting.", level=LOG_LEVELS.Debug)
|
||||
log(f"Partition {self} is not marked for formatting.", level=logging.DEBUG)
|
||||
return False
|
||||
elif self.target_mountpoint == '/boot':
|
||||
try:
|
||||
if self.has_content():
|
||||
log(f"Partition {self} is a boot partition and has content inside.", level=LOG_LEVELS.Debug)
|
||||
log(f"Partition {self} is a boot partition and has content inside.", level=logging.DEBUG)
|
||||
return False
|
||||
except SysCallError as err:
|
||||
log(err.message, LOG_LEVELS.Debug)
|
||||
log(f"Partition {self} was identified as /boot but we could not mount to check for content, continuing!", level=LOG_LEVELS.Debug)
|
||||
log(err.message, logging.DEBUG)
|
||||
log(f"Partition {self} was identified as /boot but we could not mount to check for content, continuing!", level=logging.DEBUG)
|
||||
pass
|
||||
|
||||
return True
|
||||
|
|
@ -292,7 +290,7 @@ class Partition():
|
|||
raise DiskError(f"Attempting to encrypt a partition that was not marked for encryption: {self}")
|
||||
|
||||
if not self.safe_to_format():
|
||||
log(f"Partition {self} was marked as protected but encrypt() was called on it!", level=LOG_LEVELS.Error, fg="red")
|
||||
log(f"Partition {self} was marked as protected but encrypt() was called on it!", level=logging.ERROR, fg="red")
|
||||
return False
|
||||
|
||||
handle = luks2(self, None, None)
|
||||
|
|
@ -320,7 +318,7 @@ class Partition():
|
|||
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_formatting:
|
||||
log(f'Formatting {path} -> {filesystem}', level=LOG_LEVELS.Info)
|
||||
log(f'Formatting {path} -> {filesystem}', level=logging.INFO)
|
||||
|
||||
if filesystem == 'btrfs':
|
||||
o = b''.join(sys_command(f'/usr/bin/mkfs.btrfs -f {path}'))
|
||||
|
|
@ -375,7 +373,7 @@ class Partition():
|
|||
|
||||
def mount(self, target, fs=None, options=''):
|
||||
if not self.mountpoint:
|
||||
log(f'Mounting {self} to {target}', level=LOG_LEVELS.Info)
|
||||
log(f'Mounting {self} to {target}', level=logging.INFO)
|
||||
if not fs:
|
||||
if not self.filesystem: raise DiskError(f'Need to format (or define) the filesystem on {self} before mounting.')
|
||||
fs = self.filesystem
|
||||
|
|
@ -427,25 +425,30 @@ class Filesystem():
|
|||
# TODO:
|
||||
# When instance of a HDD is selected, check all usages and gracefully unmount them
|
||||
# as well as close any crypto handles.
|
||||
def __init__(self, blockdevice, mode=GPT):
|
||||
def __init__(self, blockdevice,mode):
|
||||
self.blockdevice = blockdevice
|
||||
self.mode = mode
|
||||
|
||||
def __enter__(self, *args, **kwargs):
|
||||
if self.blockdevice.keep_partitions is False:
|
||||
log(f'Wiping {self.blockdevice} by using partition format {self.mode}', level=LOG_LEVELS.Debug)
|
||||
log(f'Wiping {self.blockdevice} by using partition format {self.mode}', level=logging.DEBUG)
|
||||
if self.mode == GPT:
|
||||
if self.raw_parted(f'{self.blockdevice.device} mklabel gpt').exit_code == 0:
|
||||
self.blockdevice.flush_cache()
|
||||
return self
|
||||
else:
|
||||
raise DiskError(f'Problem setting the partition format to GPT:', f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt')
|
||||
elif self.mode == MBR:
|
||||
if sys_command(f'/usr/bin/parted -s {self.blockdevice.device} mklabel msdos').exit_code == 0:
|
||||
return self
|
||||
else:
|
||||
raise DiskError(f'Problem setting the partition format to GPT:', f'/usr/bin/parted -s {self.blockdevice.device} mklabel msdos')
|
||||
else:
|
||||
raise DiskError(f'Unknown mode selected to format in: {self.mode}')
|
||||
|
||||
# TODO: partition_table_type is hardcoded to GPT at the moment. This has to be changed.
|
||||
elif self.mode == self.blockdevice.partition_table_type:
|
||||
log(f'Kept partition format {self.mode} for {self.blockdevice}', level=LOG_LEVELS.Debug)
|
||||
log(f'Kept partition format {self.mode} for {self.blockdevice}', level=logging.DEBUG)
|
||||
else:
|
||||
raise DiskError(f'The selected partition table format {self.mode} does not match that of {self.blockdevice}.')
|
||||
|
||||
|
|
@ -468,7 +471,6 @@ class Filesystem():
|
|||
|
||||
def raw_parted(self, string:str):
|
||||
x = sys_command(f'/usr/bin/parted -s {string}')
|
||||
log(f"'parted -s {string}' returned: {b''.join(x)}", level=LOG_LEVELS.Debug)
|
||||
return x
|
||||
|
||||
def parted(self, string:str):
|
||||
|
|
@ -481,29 +483,40 @@ class Filesystem():
|
|||
return self.raw_parted(string).exit_code
|
||||
|
||||
def use_entire_disk(self, root_filesystem_type='ext4'):
|
||||
log(f"Using and formatting the entire {self.blockdevice}.", level=LOG_LEVELS.Debug)
|
||||
self.add_partition('primary', start='1MiB', end='513MiB', format='fat32')
|
||||
self.set_name(0, 'EFI')
|
||||
self.set(0, 'boot on')
|
||||
# TODO: Probably redundant because in GPT mode 'esp on' is an alias for "boot on"?
|
||||
# https://www.gnu.org/software/parted/manual/html_node/set.html
|
||||
self.set(0, 'esp on')
|
||||
self.add_partition('primary', start='513MiB', end='100%')
|
||||
log(f"Using and formatting the entire {self.blockdevice}.", level=logging.DEBUG)
|
||||
if hasUEFI():
|
||||
self.add_partition('primary', start='1MiB', end='513MiB', format='fat32')
|
||||
self.set_name(0, 'EFI')
|
||||
self.set(0, 'boot on')
|
||||
# TODO: Probably redundant because in GPT mode 'esp on' is an alias for "boot on"?
|
||||
# https://www.gnu.org/software/parted/manual/html_node/set.html
|
||||
self.set(0, 'esp on')
|
||||
self.add_partition('primary', start='513MiB', end='100%')
|
||||
|
||||
self.blockdevice.partition[0].filesystem = 'vfat'
|
||||
self.blockdevice.partition[1].filesystem = root_filesystem_type
|
||||
log(f"Set the root partition {self.blockdevice.partition[1]} to use filesystem {root_filesystem_type}.", level=LOG_LEVELS.Debug)
|
||||
self.blockdevice.partition[0].filesystem = 'vfat'
|
||||
self.blockdevice.partition[1].filesystem = root_filesystem_type
|
||||
log(f"Set the root partition {self.blockdevice.partition[1]} to use filesystem {root_filesystem_type}.", level=logging.DEBUG)
|
||||
|
||||
self.blockdevice.partition[0].target_mountpoint = '/boot'
|
||||
self.blockdevice.partition[1].target_mountpoint = '/'
|
||||
self.blockdevice.partition[0].target_mountpoint = '/boot'
|
||||
self.blockdevice.partition[1].target_mountpoint = '/'
|
||||
|
||||
self.blockdevice.partition[0].allow_formatting = True
|
||||
self.blockdevice.partition[1].allow_formatting = True
|
||||
self.blockdevice.partition[0].allow_formatting = True
|
||||
self.blockdevice.partition[1].allow_formatting = True
|
||||
else:
|
||||
#we don't need a seprate boot partition it would be a waste of space
|
||||
self.add_partition('primary', start='1MB', end='100%')
|
||||
self.blockdevice.partition[0].filesystem=root_filesystem_type
|
||||
log(f"Set the root partition {self.blockdevice.partition[0]} to use filesystem {root_filesystem_type}.", level=logging.DEBUG)
|
||||
self.blockdevice.partition[0].target_mountpoint = '/'
|
||||
self.blockdevice.partition[0].allow_formatting = True
|
||||
|
||||
def add_partition(self, type, start, end, format=None):
|
||||
log(f'Adding partition to {self.blockdevice}', level=LOG_LEVELS.Info)
|
||||
log(f'Adding partition to {self.blockdevice}', level=logging.INFO)
|
||||
|
||||
previous_partitions = self.blockdevice.partitions
|
||||
if self.mode == MBR:
|
||||
if len(self.blockdevice.partitions)>3:
|
||||
DiskError("Too many partitions on disk, MBR disks can only have 3 parimary partitions")
|
||||
if format:
|
||||
partitioning = self.parted(f'{self.blockdevice.device} mkpart {type} {format} {start} {end}') == 0
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import os, json, hashlib, shlex, sys
|
||||
import time, pty
|
||||
import time, pty, logging
|
||||
from datetime import datetime, date
|
||||
from subprocess import Popen, STDOUT, PIPE, check_output
|
||||
from select import epoll, EPOLLIN, EPOLLHUP
|
||||
from .exceptions import *
|
||||
from .output import log, LOG_LEVELS
|
||||
from .output import log
|
||||
|
||||
def gen_uid(entropy_length=256):
|
||||
return hashlib.sha512(os.urandom(entropy_length)).hexdigest()
|
||||
|
|
@ -76,7 +76,7 @@ class sys_command():#Thread):
|
|||
"""
|
||||
Stolen from archinstall_gui
|
||||
"""
|
||||
def __init__(self, cmd, callback=None, start_callback=None, environment_vars={}, *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("emulate", False)
|
||||
kwargs.setdefault("suppress_errors", False)
|
||||
|
|
@ -84,15 +84,24 @@ class sys_command():#Thread):
|
|||
self.log = kwargs.get('log', log)
|
||||
|
||||
if kwargs['emulate']:
|
||||
self.log(f"Starting command '{cmd}' in emulation mode.", level=LOG_LEVELS.Debug)
|
||||
self.log(f"Starting command '{cmd}' in emulation mode.", level=logging.DEBUG)
|
||||
|
||||
if type(cmd) is list:
|
||||
# if we get a list of arguments
|
||||
self.raw_cmd = shlex.join(cmd)
|
||||
self.cmd = cmd
|
||||
else:
|
||||
# else consider it a single shell string
|
||||
# this should only be used if really necessary
|
||||
self.raw_cmd = cmd
|
||||
try:
|
||||
self.cmd = shlex.split(cmd)
|
||||
except Exception as e:
|
||||
raise ValueError(f'Incorrect string to split: {cmd}\n{e}')
|
||||
|
||||
self.raw_cmd = cmd
|
||||
try:
|
||||
self.cmd = shlex.split(cmd)
|
||||
except Exception as e:
|
||||
raise ValueError(f'Incorrect string to split: {cmd}\n{e}')
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.peak_output = peak_output
|
||||
self.environment_vars = environment_vars
|
||||
|
||||
self.kwargs.setdefault("worker", None)
|
||||
|
|
@ -151,6 +160,38 @@ class sys_command():#Thread):
|
|||
'exit_code': self.exit_code
|
||||
}
|
||||
|
||||
def peak(self, output :str):
|
||||
if type(output) == bytes:
|
||||
try:
|
||||
output = output.decode('UTF-8')
|
||||
except UnicodeDecodeError:
|
||||
return None
|
||||
|
||||
output = output.strip('\r\n ')
|
||||
if len(output) <= 0:
|
||||
return None
|
||||
|
||||
if self.peak_output:
|
||||
from .user_interaction import get_terminal_width
|
||||
|
||||
# Move back to the beginning of the terminal
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write("\033[%dG" % 0)
|
||||
sys.stdout.flush()
|
||||
|
||||
# Clear the line
|
||||
sys.stdout.write(" " * get_terminal_width())
|
||||
sys.stdout.flush()
|
||||
|
||||
# Move back to the beginning again
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write("\033[%dG" % 0)
|
||||
sys.stdout.flush()
|
||||
|
||||
# And print the new output we're peaking on:
|
||||
sys.stdout.write(output)
|
||||
sys.stdout.flush()
|
||||
|
||||
def run(self):
|
||||
self.status = 'running'
|
||||
old_dir = os.getcwd()
|
||||
|
|
@ -163,7 +204,7 @@ class sys_command():#Thread):
|
|||
os.execve(self.cmd[0], self.cmd, {**os.environ, **self.environment_vars})
|
||||
except FileNotFoundError:
|
||||
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=logging.DEBUG)
|
||||
self.exit_code = 1
|
||||
return False
|
||||
|
||||
|
|
@ -173,8 +214,8 @@ class sys_command():#Thread):
|
|||
poller.register(child_fd, EPOLLIN | EPOLLHUP)
|
||||
|
||||
if 'events' in self.kwargs and 'debug' in self.kwargs:
|
||||
self.log(f'[D] Using triggers for command: {self.cmd}', level=LOG_LEVELS.Debug)
|
||||
self.log(json.dumps(self.kwargs['events']), level=LOG_LEVELS.Debug)
|
||||
self.log(f'[D] Using triggers for command: {self.cmd}', level=logging.DEBUG)
|
||||
self.log(json.dumps(self.kwargs['events']), level=logging.DEBUG)
|
||||
|
||||
alive = True
|
||||
last_trigger_pos = 0
|
||||
|
|
@ -182,13 +223,14 @@ class sys_command():#Thread):
|
|||
for fileno, event in poller.poll(0.1):
|
||||
try:
|
||||
output = os.read(child_fd, 8192)
|
||||
self.peak(output)
|
||||
self.trace_log += output
|
||||
except OSError:
|
||||
alive = False
|
||||
break
|
||||
|
||||
if 'debug' in self.kwargs and self.kwargs['debug'] and len(output):
|
||||
self.log(self.cmd, 'gave:', output.decode('UTF-8'), level=LOG_LEVELS.Debug)
|
||||
self.log(self.cmd, 'gave:', output.decode('UTF-8'), level=logging.DEBUG)
|
||||
|
||||
if 'on_output' in self.kwargs:
|
||||
self.kwargs['on_output'](self.kwargs['worker'], output)
|
||||
|
|
@ -209,8 +251,8 @@ class sys_command():#Thread):
|
|||
trigger_pos = self.trace_log[last_trigger_pos:].lower().find(trigger.lower())
|
||||
|
||||
if 'debug' in self.kwargs and self.kwargs['debug']:
|
||||
self.log(f"Writing to subprocess {self.cmd[0]}: {self.kwargs['events'][trigger].decode('UTF-8')}", level=LOG_LEVELS.Debug)
|
||||
self.log(f"Writing to subprocess {self.cmd[0]}: {self.kwargs['events'][trigger].decode('UTF-8')}", level=LOG_LEVELS.Debug)
|
||||
self.log(f"Writing to subprocess {self.cmd[0]}: {self.kwargs['events'][trigger].decode('UTF-8')}", level=logging.DEBUG)
|
||||
self.log(f"Writing to subprocess {self.cmd[0]}: {self.kwargs['events'][trigger].decode('UTF-8')}", level=logging.DEBUG)
|
||||
|
||||
last_trigger_pos = trigger_pos
|
||||
os.write(child_fd, self.kwargs['events'][trigger])
|
||||
|
|
@ -224,18 +266,18 @@ class sys_command():#Thread):
|
|||
## Adding a exit trigger:
|
||||
if len(self.kwargs['events']) == 0:
|
||||
if 'debug' in self.kwargs and self.kwargs['debug']:
|
||||
self.log(f"Waiting for last command {self.cmd[0]} to finish.", level=LOG_LEVELS.Debug)
|
||||
self.log(f"Waiting for last command {self.cmd[0]} to finish.", level=logging.DEBUG)
|
||||
|
||||
if bytes(f']$'.lower(), 'UTF-8') in self.trace_log[0-len(f']$')-5:].lower():
|
||||
if 'debug' in self.kwargs and self.kwargs['debug']:
|
||||
self.log(f"{self.cmd[0]} has finished.", level=LOG_LEVELS.Debug)
|
||||
self.log(f"{self.cmd[0]} has finished.", level=logging.DEBUG)
|
||||
alive = False
|
||||
break
|
||||
|
||||
self.status = 'done'
|
||||
|
||||
if 'debug' in self.kwargs and self.kwargs['debug']:
|
||||
self.log(f"{self.cmd[0]} waiting for exit code.", level=LOG_LEVELS.Debug)
|
||||
self.log(f"{self.cmd[0]} waiting for exit code.", level=logging.DEBUG)
|
||||
|
||||
if not self.kwargs['emulate']:
|
||||
try:
|
||||
|
|
@ -249,14 +291,14 @@ class sys_command():#Thread):
|
|||
self.exit_code = 0
|
||||
|
||||
if 'debug' in self.kwargs and self.kwargs['debug']:
|
||||
self.log(f"{self.cmd[0]} got exit code: {self.exit_code}", level=LOG_LEVELS.Debug)
|
||||
self.log(f"{self.cmd[0]} got exit code: {self.exit_code}", level=logging.DEBUG)
|
||||
|
||||
if 'ignore_errors' in self.kwargs:
|
||||
self.exit_code = 0
|
||||
|
||||
if self.exit_code != 0 and not self.kwargs['suppress_errors']:
|
||||
#self.log(self.trace_log.decode('UTF-8'), level=LOG_LEVELS.Debug)
|
||||
#self.log(f"'{self.raw_cmd}' did not exit gracefully, exit code {self.exit_code}.", level=LOG_LEVELS.Error)
|
||||
#self.log(self.trace_log.decode('UTF-8'), level=logging.DEBUG)
|
||||
#self.log(f"'{self.raw_cmd}' did not exit gracefully, exit code {self.exit_code}.", level=logging.ERROR)
|
||||
raise SysCallError(message=f"{self.trace_log.decode('UTF-8')}\n'{self.raw_cmd}' did not exit gracefully (trace log above), exit code: {self.exit_code}", exit_code=self.exit_code)
|
||||
|
||||
self.ended = time.time()
|
||||
|
|
|
|||
|
|
@ -1,14 +1,44 @@
|
|||
import os
|
||||
import os, subprocess, json
|
||||
from .general import sys_command
|
||||
from .networking import list_interfaces, enrichIfaceTypes
|
||||
from typing import Optional
|
||||
|
||||
def hasWifi():
|
||||
__packages__ = ['xf86-video-amdgpu', 'xf86-video-ati', 'xf86-video-intel', 'xf86-video-nouveau', 'xf86-video-fbdev', 'xf86-video-vesa', 'xf86-video-vmware', 'nvidia', 'mesa']
|
||||
|
||||
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 / virtualbox' : ['xf86-video-vmware']
|
||||
}
|
||||
|
||||
def hasWifi()->bool:
|
||||
return 'WIRELESS' in enrichIfaceTypes(list_interfaces().values()).values()
|
||||
|
||||
def hasUEFI():
|
||||
def hasAMDCPU()->bool:
|
||||
if subprocess.check_output("lscpu | grep AMD", shell=True).strip().decode():
|
||||
return True
|
||||
return False
|
||||
def hasIntelCPU()->bool:
|
||||
if subprocess.check_output("lscpu | grep Intel", shell=True).strip().decode():
|
||||
return True
|
||||
return False
|
||||
|
||||
def hasUEFI()->bool:
|
||||
return os.path.isdir('/sys/firmware/efi')
|
||||
|
||||
def graphicsDevices():
|
||||
def graphicsDevices()->dict:
|
||||
cards = {}
|
||||
for line in sys_command(f"lspci"):
|
||||
if b' VGA ' in line:
|
||||
|
|
@ -16,13 +46,28 @@ def graphicsDevices():
|
|||
cards[identifier.strip().lower().decode('UTF-8')] = line
|
||||
return cards
|
||||
|
||||
def hasNvidiaGraphics():
|
||||
def hasNvidiaGraphics()->bool:
|
||||
return any('nvidia' in x for x in graphicsDevices())
|
||||
|
||||
def hasAmdGraphics():
|
||||
def hasAmdGraphics()->bool:
|
||||
return any('amd' in x for x in graphicsDevices())
|
||||
|
||||
def hasIntelGraphics():
|
||||
def hasIntelGraphics()->bool:
|
||||
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 non-zero exit code if it is not on a virtual machine
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
# TODO: Add more identifiers
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import os, stat, time, shutil, pathlib, subprocess
|
||||
|
||||
import os, stat, time, shutil, pathlib
|
||||
import subprocess, logging
|
||||
from .exceptions import *
|
||||
from .disk import *
|
||||
from .general import *
|
||||
|
|
@ -7,8 +7,12 @@ from .user_interaction import *
|
|||
from .profiles import Profile
|
||||
from .mirrors import *
|
||||
from .systemd import Networkd
|
||||
from .output import log, LOG_LEVELS
|
||||
from .output import log
|
||||
from .storage import storage
|
||||
from .hardware import *
|
||||
|
||||
# Any package that the Installer() is responsible for (optional and the default ones)
|
||||
__packages__ = ["base", "base-devel", "linux-firmware", "linux", "linux-lts", "linux-zen", "linux-hardened"]
|
||||
|
||||
class Installer():
|
||||
"""
|
||||
|
|
@ -18,7 +22,7 @@ class Installer():
|
|||
:param partition: Requires a partition as the first argument, this is
|
||||
so that the installer can mount to `mountpoint` and strap packages there.
|
||||
:type partition: class:`archinstall.Partition`
|
||||
|
||||
|
||||
:param boot_partition: There's two reasons for needing a boot partition argument,
|
||||
The first being so that `mkinitcpio` can place the `vmlinuz` kernel at the right place
|
||||
during the `pacstrap` or `linux` and the base packages for a minimal installation.
|
||||
|
|
@ -29,12 +33,12 @@ class Installer():
|
|||
:param profile: A profile to install, this is optional and can be called later manually.
|
||||
This just simplifies the process by not having to call :py:func:`~archinstall.Installer.install_profile` later on.
|
||||
:type profile: str, optional
|
||||
|
||||
|
||||
:param hostname: The given /etc/hostname for the machine.
|
||||
:type hostname: str, optional
|
||||
|
||||
"""
|
||||
def __init__(self, target, *, base_packages='base base-devel linux linux-firmware efibootmgr'):
|
||||
def __init__(self, target, *, base_packages=__packages__[:3], kernels=['linux']):
|
||||
self.target = target
|
||||
self.init_time = time.strftime('%Y-%m-%d_%H-%M-%S')
|
||||
self.milliseconds = int(str(time.time()).split('.')[1])
|
||||
|
|
@ -43,14 +47,17 @@ class Installer():
|
|||
'base' : False,
|
||||
'bootloader' : False
|
||||
}
|
||||
|
||||
|
||||
self.base_packages = base_packages.split(' ') if type(base_packages) is str else base_packages
|
||||
for kernel in kernels:
|
||||
self.base_packages.append(kernel)
|
||||
|
||||
self.post_base_install = []
|
||||
|
||||
storage['session'] = self
|
||||
self.partitions = get_partitions_in_use(self.target)
|
||||
|
||||
def log(self, *args, level=LOG_LEVELS.Debug, **kwargs):
|
||||
def log(self, *args, level=logging.DEBUG, **kwargs):
|
||||
"""
|
||||
installer.log() wraps output.log() mainly to set a default log-level for this install session.
|
||||
Any manual override can be done per log() call.
|
||||
|
|
@ -65,8 +72,8 @@ class Installer():
|
|||
# TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager
|
||||
|
||||
if len(args) >= 2 and args[1]:
|
||||
#self.log(self.trace_log.decode('UTF-8'), level=LOG_LEVELS.Debug)
|
||||
self.log(args[1], level=LOG_LEVELS.Error, fg='red')
|
||||
#self.log(self.trace_log.decode('UTF-8'), level=logging.DEBUG)
|
||||
self.log(args[1], level=logging.ERROR, fg='red')
|
||||
|
||||
self.sync_log_to_install_medium()
|
||||
|
||||
|
|
@ -79,17 +86,17 @@ class Installer():
|
|||
self.genfstab()
|
||||
|
||||
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.', fg='green', level=logging.INFO)
|
||||
self.sync_log_to_install_medium()
|
||||
|
||||
return True
|
||||
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:', fg='red', level=logging.WARNING)
|
||||
for step in missing_steps:
|
||||
self.log(f' - {step}', bg='black', fg='red', level=LOG_LEVELS.Warning)
|
||||
self.log(f' - {step}', fg='red', level=logging.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.log(f"Detailed error logs can be found at: {storage['LOG_PATH']}", level=logging.WARNING)
|
||||
self.log(f"Submit this zip file as an issue to https://github.com/archlinux/archinstall/issues", level=logging.WARNING)
|
||||
|
||||
self.sync_log_to_install_medium()
|
||||
return False
|
||||
|
|
@ -119,21 +126,21 @@ class Installer():
|
|||
|
||||
def pacstrap(self, *packages, **kwargs):
|
||||
if type(packages[0]) in (list, tuple): packages = packages[0]
|
||||
self.log(f'Installing packages: {packages}', level=LOG_LEVELS.Info)
|
||||
self.log(f'Installing packages: {packages}', level=logging.INFO)
|
||||
|
||||
if (sync_mirrors := sys_command('/usr/bin/pacman -Syy')).exit_code == 0:
|
||||
if (pacstrap := sys_command(f'/usr/bin/pacstrap {self.target} {" ".join(packages)}', **kwargs)).exit_code == 0:
|
||||
return True
|
||||
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=logging.INFO)
|
||||
else:
|
||||
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=logging.INFO)
|
||||
|
||||
def set_mirrors(self, mirrors):
|
||||
return use_mirrors(mirrors, destination=f'{self.target}/etc/pacman.d/mirrorlist')
|
||||
|
||||
def genfstab(self, flags='-pU'):
|
||||
self.log(f"Updating {self.target}/etc/fstab", level=LOG_LEVELS.Info)
|
||||
self.log(f"Updating {self.target}/etc/fstab", level=logging.INFO)
|
||||
|
||||
fstab = sys_command(f'/usr/bin/genfstab {flags} {self.target}').trace_log
|
||||
with open(f"{self.target}/etc/fstab", 'ab') as fstab_fh:
|
||||
|
|
@ -141,7 +148,7 @@ class Installer():
|
|||
|
||||
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{fstab}')
|
||||
|
||||
|
||||
return True
|
||||
|
||||
def set_hostname(self, hostname :str, *args, **kwargs):
|
||||
|
|
@ -169,19 +176,19 @@ class Installer():
|
|||
else:
|
||||
self.log(
|
||||
f"Time zone {zone} does not exist, continuing with system default.",
|
||||
level=LOG_LEVELS.Warning,
|
||||
level=logging.WARNING,
|
||||
fg='red'
|
||||
)
|
||||
|
||||
def activate_ntp(self):
|
||||
self.log(f'Installing and activating NTP.', level=LOG_LEVELS.Info)
|
||||
self.log(f'Installing and activating NTP.', level=logging.INFO)
|
||||
if self.pacstrap('ntp'):
|
||||
if self.enable_service('ntpd'):
|
||||
return True
|
||||
|
||||
def enable_service(self, *services):
|
||||
for service in services:
|
||||
self.log(f'Enabling service {service}', level=LOG_LEVELS.Info)
|
||||
self.log(f'Enabling service {service}', level=logging.INFO)
|
||||
if (output := self.arch_chroot(f'systemctl enable {service}')).exit_code != 0:
|
||||
raise ServiceException(f"Unable to start service {service}: {output}")
|
||||
|
||||
|
|
@ -189,6 +196,9 @@ class Installer():
|
|||
return sys_command(f'/usr/bin/arch-chroot {self.target} {cmd}')
|
||||
|
||||
def arch_chroot(self, cmd, *args, **kwargs):
|
||||
if 'runas' in kwargs:
|
||||
cmd = f"su - {kwargs['runas']} -c \"{cmd}\""
|
||||
|
||||
return self.run_command(cmd)
|
||||
|
||||
def drop_to_shell(self):
|
||||
|
|
@ -223,7 +233,7 @@ class Installer():
|
|||
# If we haven't installed the base yet (function called pre-maturely)
|
||||
if self.helper_flags.get('base', False) is False:
|
||||
self.base_packages.append('iwd')
|
||||
# This function will be called after minimal_installation()
|
||||
# This function will be called after minimal_installation()
|
||||
# as a hook for post-installs. This hook is only needed if
|
||||
# base is not installed yet.
|
||||
def post_install_enable_iwd_service(*args, **kwargs):
|
||||
|
|
@ -273,6 +283,7 @@ class Installer():
|
|||
## (encrypted partitions default to btrfs for now, so we need btrfs-progs)
|
||||
## TODO: Perhaps this should be living in the function which dictates
|
||||
## the partitioning. Leaving here for now.
|
||||
|
||||
MODULES = []
|
||||
BINARIES = []
|
||||
FILES = []
|
||||
|
|
@ -298,10 +309,20 @@ class Installer():
|
|||
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.pacstrap(self.base_packages)
|
||||
self.helper_flags['base-strapped'] = True
|
||||
#self.genfstab()
|
||||
|
||||
if not isVM():
|
||||
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(
|
||||
"\ntmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0\n"
|
||||
|
|
@ -322,13 +343,13 @@ class Installer():
|
|||
mkinit.write(f"BINARIES=({' '.join(BINARIES)})\n")
|
||||
mkinit.write(f"FILES=({' '.join(FILES)})\n")
|
||||
mkinit.write(f"HOOKS=({' '.join(HOOKS)})\n")
|
||||
sys_command(f'/usr/bin/arch-chroot {self.target} mkinitcpio -p linux')
|
||||
sys_command(f'/usr/bin/arch-chroot {self.target} mkinitcpio -P')
|
||||
|
||||
self.helper_flags['base'] = True
|
||||
|
||||
# Run registered post-install hooks
|
||||
for function in self.post_base_install:
|
||||
self.log(f"Running post-installation hook: {function}", level=LOG_LEVELS.Info)
|
||||
self.log(f"Running post-installation hook: {function}", level=logging.INFO)
|
||||
function(self)
|
||||
|
||||
return True
|
||||
|
|
@ -342,9 +363,13 @@ class Installer():
|
|||
elif partition.mountpoint == self.target:
|
||||
root_partition = partition
|
||||
|
||||
self.log(f'Adding bootloader {bootloader} to {boot_partition}', level=LOG_LEVELS.Info)
|
||||
self.log(f'Adding bootloader {bootloader} to {boot_partition if boot_partition else root_partition}', level=logging.INFO)
|
||||
|
||||
if bootloader == 'systemd-bootctl':
|
||||
self.pacstrap('efibootmgr')
|
||||
|
||||
if not hasUEFI():
|
||||
raise HardwareIncompatibilityError
|
||||
# TODO: Ideally we would want to check if another config
|
||||
# points towards the same disk and/or partition.
|
||||
# And in which case we should do some clean up.
|
||||
|
|
@ -372,13 +397,20 @@ class Installer():
|
|||
## For some reason, blkid and /dev/disk/by-uuid are not getting along well.
|
||||
## 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()
|
||||
|
||||
# Setup the loader 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 on: {self.init_time}\n')
|
||||
entry.write(f'title Arch 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')
|
||||
## blkid doesn't trigger on loopback devices really well,
|
||||
## so we'll use the old manual method until we get that sorted out.
|
||||
|
|
@ -387,18 +419,34 @@ class Installer():
|
|||
if (real_device := self.detect_encryption(root_partition)):
|
||||
# TODO: We need to detect if the encrypted device is a whole disk encryption,
|
||||
# or simply a partition encryption. Right now we assume it's a partition (and we always have)
|
||||
log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.uuid}'.", level=LOG_LEVELS.Debug)
|
||||
log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.uuid}'.", level=logging.DEBUG)
|
||||
entry.write(f'options cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n')
|
||||
else:
|
||||
log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.uuid}'.", level=LOG_LEVELS.Debug)
|
||||
log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.uuid}'.", level=logging.DEBUG)
|
||||
entry.write(f'options root=PARTUUID={root_partition.uuid} rw intel_pstate=no_hwp\n')
|
||||
|
||||
self.helper_flags['bootloader'] = bootloader
|
||||
return True
|
||||
|
||||
raise RequirementError(f"Could not identify the UUID of {root_partition}, there for {self.target}/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":
|
||||
self.pacstrap('grub')
|
||||
|
||||
if hasUEFI():
|
||||
self.pacstrap('efibootmgr')
|
||||
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 /mnt grub-mkconfig -o /boot/grub/grub.cfg')
|
||||
return True
|
||||
else:
|
||||
root_device = subprocess.check_output(f'basename "$(readlink -f /sys/class/block/{root_partition.path.replace("/dev/","")}/..)"', shell=True).decode().strip()
|
||||
if root_device == "block":
|
||||
root_device = f"{root_partition.path}"
|
||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} grub-install --target=i386-pc /dev/{root_device}'))
|
||||
sys_command('/usr/bin/arch-chroot /mnt grub-mkconfig -o /boot/grub/grub.cfg')
|
||||
self.helper_flags['bootloader'] = bootloader
|
||||
return True
|
||||
else:
|
||||
raise RequirementError(f"Unknown (or not yet implemented) bootloader added to add_bootloader(): {bootloader}")
|
||||
raise RequirementError(f"Unknown (or not yet implemented) bootloader requested: {bootloader}")
|
||||
|
||||
def add_additional_packages(self, *packages):
|
||||
return self.pacstrap(*packages)
|
||||
|
|
@ -416,17 +464,17 @@ class Installer():
|
|||
if type(profile) == str:
|
||||
profile = Profile(self, profile)
|
||||
|
||||
self.log(f'Installing network profile {profile}', level=LOG_LEVELS.Info)
|
||||
self.log(f'Installing network profile {profile}', level=logging.INFO)
|
||||
return profile.install()
|
||||
|
||||
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=logging.INFO)
|
||||
with open(f'{self.target}/etc/sudoers', 'a') as sudoers:
|
||||
sudoers.write(f'{"%" if group else ""}{entity} ALL=(ALL) ALL\n')
|
||||
return True
|
||||
|
||||
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=logging.INFO)
|
||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} useradd -m -G wheel {user}'))
|
||||
if password:
|
||||
self.user_set_pw(user, password)
|
||||
|
|
@ -439,7 +487,7 @@ class Installer():
|
|||
self.helper_flags['user'] = True
|
||||
|
||||
def user_set_pw(self, user, password):
|
||||
self.log(f'Setting password for {user}', level=LOG_LEVELS.Info)
|
||||
self.log(f'Setting password for {user}', level=logging.INFO)
|
||||
|
||||
if user == 'root':
|
||||
# This means the root account isn't locked/disabled with * in /etc/passwd
|
||||
|
|
@ -449,7 +497,7 @@ class Installer():
|
|||
pass
|
||||
|
||||
def user_set_shell(self, user, shell):
|
||||
self.log(f'Setting shell for {user} to {shell}', level=LOG_LEVELS.Info)
|
||||
self.log(f'Setting shell for {user} to {shell}', level=logging.INFO)
|
||||
|
||||
o = b''.join(sys_command(f"/usr/bin/arch-chroot {self.target} sh -c \"chsh -s {shell} {user}\""))
|
||||
pass
|
||||
|
|
@ -460,5 +508,5 @@ class Installer():
|
|||
vconsole.write(f'KEYMAP={language}\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)
|
||||
self.log(f'Keyboard language was not changed from default (no language specified).', fg="yellow", level=logging.INFO)
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@ def list_keyboard_languages():
|
|||
if os.path.splitext(file)[1] == '.gz':
|
||||
yield file.strip('.gz').strip('.map')
|
||||
|
||||
def verify_keyboard_layout(layout):
|
||||
for language in list_keyboard_languages():
|
||||
if layout.lower() == language.lower():
|
||||
return True
|
||||
return False
|
||||
|
||||
def search_keyboard_layout(filter):
|
||||
for language in list_keyboard_languages():
|
||||
if filter.lower() in language.lower():
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ import os
|
|||
import shlex
|
||||
import time
|
||||
import pathlib
|
||||
import logging
|
||||
from .exceptions import *
|
||||
from .general import *
|
||||
from .disk import Partition
|
||||
from .output import log, LOG_LEVELS
|
||||
from .output import log
|
||||
from .storage import storage
|
||||
|
||||
class luks2():
|
||||
|
|
@ -48,7 +49,7 @@ class luks2():
|
|||
if not self.partition.allow_formatting:
|
||||
raise DiskError(f'Could not encrypt volume {self.partition} due to it having a formatting lock.')
|
||||
|
||||
log(f'Encrypting {partition} (This might take a while)', level=LOG_LEVELS.Info)
|
||||
log(f'Encrypting {partition} (This might take a while)', level=logging.INFO)
|
||||
|
||||
if not key_file:
|
||||
if self.key_file:
|
||||
|
|
@ -70,7 +71,7 @@ class luks2():
|
|||
'--batch-mode',
|
||||
'--verbose',
|
||||
'--type', 'luks2',
|
||||
'--pbkdf', 'argon2i',
|
||||
'--pbkdf', 'argon2id',
|
||||
'--hash', hash_type,
|
||||
'--key-size', str(key_size),
|
||||
'--iter-time', str(iter_time),
|
||||
|
|
@ -84,7 +85,7 @@ class luks2():
|
|||
cmd_handle = sys_command(cryptsetup_args)
|
||||
except SysCallError as err:
|
||||
if err.exit_code == 256:
|
||||
log(f'{partition} is being used, trying to unmount and crypt-close the device and running one more attempt at encrypting the device.', level=LOG_LEVELS.Debug)
|
||||
log(f'{partition} is being used, trying to unmount and crypt-close the device and running one more attempt at encrypting the device.', level=logging.DEBUG)
|
||||
# Partition was in use, unmount it and try again
|
||||
partition.unmount()
|
||||
|
||||
|
|
@ -97,11 +98,11 @@ class luks2():
|
|||
for child in children:
|
||||
# Unmount the child location
|
||||
if child_mountpoint := child.get('mountpoint', None):
|
||||
log(f'Unmounting {child_mountpoint}', level=LOG_LEVELS.Debug)
|
||||
log(f'Unmounting {child_mountpoint}', level=logging.DEBUG)
|
||||
sys_command(f"umount -R {child_mountpoint}")
|
||||
|
||||
# And close it if possible.
|
||||
log(f"Closing crypt device {child['name']}", level=LOG_LEVELS.Debug)
|
||||
log(f"Closing crypt device {child['name']}", level=logging.DEBUG)
|
||||
sys_command(f"cryptsetup close {child['name']}")
|
||||
|
||||
# Then try again to set up the crypt-device
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import urllib.request
|
||||
import urllib.request, logging
|
||||
|
||||
from .exceptions import *
|
||||
from .general import *
|
||||
|
|
@ -59,7 +59,7 @@ def insert_mirrors(mirrors, *args, **kwargs):
|
|||
return True
|
||||
|
||||
def use_mirrors(regions :dict, destination='/etc/pacman.d/mirrorlist'):
|
||||
log(f'A new package mirror-list has been created: {destination}', level=LOG_LEVELS.Info)
|
||||
log(f'A new package mirror-list has been created: {destination}', level=logging.INFO)
|
||||
for region, mirrors in regions.items():
|
||||
with open(destination, 'w') as mirrorlist:
|
||||
for mirror in mirrors:
|
||||
|
|
@ -79,7 +79,7 @@ def list_mirrors():
|
|||
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")
|
||||
log(f'Could not fetch an active mirror-list: {err}', level=logging.WARNING, fg="yellow")
|
||||
return regions
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,32 @@ class LOG_LEVELS:
|
|||
|
||||
class journald(dict):
|
||||
@abc.abstractmethod
|
||||
def log(message, level=LOG_LEVELS.Debug):
|
||||
import systemd.journal
|
||||
def log(message, level=logging.DEBUG):
|
||||
try:
|
||||
import systemd.journal
|
||||
except ModuleNotFoundError:
|
||||
return False
|
||||
|
||||
# For backwards compability, convert old style log-levels
|
||||
# to logging levels (and warn about deprecated usage)
|
||||
# There's some code re-usage here but that should be fine.
|
||||
# TODO: Remove these in a few versions:
|
||||
if level == LOG_LEVELS.Critical:
|
||||
log("Deprecated level detected in log message, please use new logging.<level> instead for the following log message:", fg="red", level=logging.ERROR, force=True)
|
||||
level = logging.CRITICAL
|
||||
elif level == LOG_LEVELS.Error:
|
||||
log("Deprecated level detected in log message, please use new logging.<level> instead for the following log message:", fg="red", level=logging.ERROR, force=True)
|
||||
level = logging.ERROR
|
||||
elif level == LOG_LEVELS.Warning:
|
||||
log("Deprecated level detected in log message, please use new logging.<level> instead for the following log message:", fg="red", level=logging.ERROR, force=True)
|
||||
level = logging.WARNING
|
||||
elif level == LOG_LEVELS.Info:
|
||||
log("Deprecated level detected in log message, please use new logging.<level> instead for the following log message:", fg="red", level=logging.ERROR, force=True)
|
||||
level = logging.INFO
|
||||
elif level == LOG_LEVELS.Debug:
|
||||
log("Deprecated level detected in log message, please use new logging.<level> instead for the following log message:", fg="red", level=logging.ERROR, force=True)
|
||||
level = logging.DEBUG
|
||||
|
||||
log_adapter = logging.getLogger('archinstall')
|
||||
log_fmt = logging.Formatter("[%(levelname)s]: %(message)s")
|
||||
log_ch = systemd.journal.JournalHandler()
|
||||
|
|
@ -26,19 +50,7 @@ class journald(dict):
|
|||
log_adapter.addHandler(log_ch)
|
||||
log_adapter.setLevel(logging.DEBUG)
|
||||
|
||||
if level == LOG_LEVELS.Critical:
|
||||
log_adapter.critical(message)
|
||||
elif level == LOG_LEVELS.Error:
|
||||
log_adapter.error(message)
|
||||
elif level == LOG_LEVELS.Warning:
|
||||
log_adapter.warning(message)
|
||||
elif level == LOG_LEVELS.Info:
|
||||
log_adapter.info(message)
|
||||
elif level == LOG_LEVELS.Debug:
|
||||
log_adapter.debug(message)
|
||||
else:
|
||||
# Fallback logger
|
||||
log_adapter.debug(message)
|
||||
log_adapter.log(level, message)
|
||||
|
||||
# TODO: Replace log() for session based logging.
|
||||
class SessionLogging():
|
||||
|
|
@ -95,19 +107,19 @@ def log(*args, **kwargs):
|
|||
# we use that one to output everything
|
||||
if (filename := storage.get('LOG_FILE', None)):
|
||||
absolute_logfile = os.path.join(storage.get('LOG_PATH', './'), filename)
|
||||
if not os.path.isfile(absolute_logfile):
|
||||
try:
|
||||
Path(absolute_logfile).parents[0].mkdir(exist_ok=True, parents=True)
|
||||
except PermissionError:
|
||||
# Fallback to creating the log file in the current folder
|
||||
err_string = f"Not enough permission to place log file at {absolute_logfile}, creating it in {Path('./').absolute()/filename} instead."
|
||||
absolute_logfile = Path('./').absolute()/filename
|
||||
absolute_logfile.parents[0].mkdir(exist_ok=True)
|
||||
absolute_logfile = str(absolute_logfile)
|
||||
storage['LOG_PATH'] = './'
|
||||
log(err_string, fg="red")
|
||||
|
||||
Path(absolute_logfile).touch() # Overkill?
|
||||
try:
|
||||
Path(absolute_logfile).parents[0].mkdir(exist_ok=True, parents=True)
|
||||
with open(absolute_logfile, 'a') as log_file:
|
||||
log_file.write("")
|
||||
except PermissionError:
|
||||
# Fallback to creating the log file in the current folder
|
||||
err_string = f"Not enough permission to place log file at {absolute_logfile}, creating it in {Path('./').absolute()/filename} instead."
|
||||
absolute_logfile = Path('./').absolute()/filename
|
||||
absolute_logfile.parents[0].mkdir(exist_ok=True)
|
||||
absolute_logfile = str(absolute_logfile)
|
||||
storage['LOG_PATH'] = './'
|
||||
log(err_string, fg="red")
|
||||
|
||||
with open(absolute_logfile, 'a') as log_file:
|
||||
log_file.write(f"{orig_string}\n")
|
||||
|
|
@ -116,13 +128,33 @@ def log(*args, **kwargs):
|
|||
# Unless the level is higher than we've decided to output interactively.
|
||||
# (Remember, log files still get *ALL* the output despite level restrictions)
|
||||
if 'level' in kwargs:
|
||||
if kwargs['level'] > storage.get('LOG_LEVEL', LOG_LEVELS.Info):
|
||||
# For backwards compability, convert old style log-levels
|
||||
# to logging levels (and warn about deprecated usage)
|
||||
# There's some code re-usage here but that should be fine.
|
||||
# TODO: Remove these in a few versions:
|
||||
if kwargs['level'] == LOG_LEVELS.Critical:
|
||||
log("Deprecated level detected in log message, please use new logging.<level> instead for the following log message:", fg="red", level=logging.ERROR, force=True)
|
||||
kwargs['level'] = logging.CRITICAL
|
||||
elif kwargs['level'] == LOG_LEVELS.Error:
|
||||
log("Deprecated level detected in log message, please use new logging.<level> instead for the following log message:", fg="red", level=logging.ERROR, force=True)
|
||||
kwargs['level'] = logging.ERROR
|
||||
elif kwargs['level'] == LOG_LEVELS.Warning:
|
||||
log("Deprecated level detected in log message, please use new logging.<level> instead for the following log message:", fg="red", level=logging.ERROR, force=True)
|
||||
kwargs['level'] = logging.WARNING
|
||||
elif kwargs['level'] == LOG_LEVELS.Info:
|
||||
log("Deprecated level detected in log message, please use new logging.<level> instead for the following log message:", fg="red", level=logging.ERROR, force=True)
|
||||
kwargs['level'] = logging.INFO
|
||||
elif kwargs['level'] == LOG_LEVELS.Debug:
|
||||
log("Deprecated level detected in log message, please use new logging.<level> instead for the following log message:", fg="red", level=logging.ERROR, force=True)
|
||||
kwargs['level'] = logging.DEBUG
|
||||
|
||||
if kwargs['level'] > storage.get('LOG_LEVEL', logging.INFO) and not 'force' in kwargs:
|
||||
# Level on log message was Debug, but output level is set to Info.
|
||||
# In that case, we'll drop it.
|
||||
return None
|
||||
|
||||
try:
|
||||
journald.log(string, level=kwargs.get('level', LOG_LEVELS.Info))
|
||||
journald.log(string, level=kwargs.get('level', logging.INFO))
|
||||
except ModuleNotFoundError:
|
||||
pass # Ignore writing to journald
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import os, urllib.request, urllib.parse, ssl, json, re
|
||||
import importlib.util, sys, glob, hashlib
|
||||
import importlib.util, sys, glob, hashlib, logging
|
||||
from collections import OrderedDict
|
||||
from .general import multisplit, sys_command
|
||||
from .exceptions import *
|
||||
from .networking import *
|
||||
from .output import log, LOG_LEVELS
|
||||
from .output import log
|
||||
from .storage import storage
|
||||
|
||||
def grab_url_data(path):
|
||||
|
|
@ -15,7 +15,7 @@ def grab_url_data(path):
|
|||
response = urllib.request.urlopen(safe_path, context=ssl_context)
|
||||
return response.read()
|
||||
|
||||
def list_profiles(filter_irrelevant_macs=True, subpath=''):
|
||||
def list_profiles(filter_irrelevant_macs=True, subpath='', filter_top_level_profiles=False):
|
||||
# TODO: Grab from github page as well, not just local static files
|
||||
if filter_irrelevant_macs:
|
||||
local_macs = list_interfaces()
|
||||
|
|
@ -63,6 +63,11 @@ def list_profiles(filter_irrelevant_macs=True, subpath=''):
|
|||
|
||||
cache[profile[:-3]] = {'path' : os.path.join(storage["UPSTREAM_URL"]+subpath, profile), 'description' : profile_list[profile], 'tailored' : tailored}
|
||||
|
||||
if filter_top_level_profiles:
|
||||
for profile in list(cache.keys()):
|
||||
if Profile(None, profile).is_top_level_profile() is False:
|
||||
del(cache[profile])
|
||||
|
||||
return cache
|
||||
|
||||
class Script():
|
||||
|
|
@ -77,7 +82,6 @@ class Script():
|
|||
self.examples = None
|
||||
self.namespace = os.path.splitext(os.path.basename(self.path))[0]
|
||||
self.original_namespace = self.namespace
|
||||
log(f"Script {self} has been loaded with namespace '{self.namespace}'", level=LOG_LEVELS.Debug)
|
||||
|
||||
def __enter__(self, *args, **kwargs):
|
||||
self.execute()
|
||||
|
|
@ -88,6 +92,9 @@ class Script():
|
|||
if len(args) >= 2 and args[1]:
|
||||
raise args[1]
|
||||
|
||||
if self.original_namespace:
|
||||
self.namespace = self.original_namespace
|
||||
|
||||
def localize_path(self, profile_path):
|
||||
if (url := urllib.parse.urlparse(profile_path)).scheme and url.scheme in ('https', 'http'):
|
||||
if not self.converted_path:
|
||||
|
|
@ -177,7 +184,7 @@ class Profile(Script):
|
|||
if hasattr(imported, '_prep_function'):
|
||||
return True
|
||||
return False
|
||||
"""
|
||||
|
||||
def has_post_install(self):
|
||||
with open(self.path, 'r') as source:
|
||||
source_data = source.read()
|
||||
|
|
@ -193,14 +200,19 @@ class Profile(Script):
|
|||
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()
|
||||
|
||||
# TODO: I imagine that there is probably a better way to write this.
|
||||
return 'top_level_profile = True' in source_data
|
||||
if '__name__' in source_data and 'is_top_level_profile' in source_data:
|
||||
with self.load_instructions(namespace=f"{self.namespace}.py") as imported:
|
||||
if hasattr(imported, 'is_top_level_profile'):
|
||||
return imported.is_top_level_profile
|
||||
|
||||
# Default to True if nothing is specified,
|
||||
# since developers like less code - omitting it should assume they want to present it.
|
||||
return True
|
||||
|
||||
@property
|
||||
def packages(self) -> list:
|
||||
|
|
@ -222,7 +234,6 @@ class Profile(Script):
|
|||
if hasattr(imported, '__packages__'):
|
||||
return imported.__packages__
|
||||
return None
|
||||
|
||||
|
||||
class Application(Profile):
|
||||
def __repr__(self, *args, **kwargs):
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
import getpass, pathlib, os, shutil, re
|
||||
import sys, time, signal
|
||||
import getpass, pathlib, os, shutil, re, time
|
||||
import sys, time, signal, ipaddress, logging
|
||||
import termios, tty, select # Used for char by char polling of sys.stdin
|
||||
from .exceptions import *
|
||||
from .profiles import Profile
|
||||
from .locale_helpers import search_keyboard_layout
|
||||
from .output import log, LOG_LEVELS
|
||||
from .locale_helpers import list_keyboard_languages, verify_keyboard_layout, search_keyboard_layout
|
||||
from .output import log
|
||||
from .storage import storage
|
||||
from .networking import list_interfaces
|
||||
from .general import sys_command
|
||||
from .hardware import AVAILABLE_GFX_DRIVERS, hasUEFI
|
||||
|
||||
## TODO: Some inconsistencies between the selection processes.
|
||||
## Some return the keys from the options, some the values?
|
||||
|
|
@ -24,7 +27,7 @@ def check_for_correct_username(username):
|
|||
return True
|
||||
log(
|
||||
"The username you entered is invalid. Try again",
|
||||
level=LOG_LEVELS.Warning,
|
||||
level=logging.WARNING,
|
||||
fg='red'
|
||||
)
|
||||
return False
|
||||
|
|
@ -93,14 +96,181 @@ def print_large_list(options, padding=5, margin_bottom=0, separator=': '):
|
|||
print(f"{str(column): >{highest_index_number_length}}{separator}{options[column]}", end = spaces)
|
||||
print()
|
||||
|
||||
def ask_for_superuser_account(prompt='Create a required super-user with sudo privileges: ', forced=False):
|
||||
return column, row
|
||||
|
||||
|
||||
def generic_multi_select(options, text="Select one or more of the options above (leave blank to continue): ", sort=True, default=None, allow_empty=False):
|
||||
if sort:
|
||||
options = sorted(options)
|
||||
|
||||
section = MiniCurses(get_terminal_width(), len(options))
|
||||
|
||||
selected_options = []
|
||||
|
||||
while True:
|
||||
if len(selected_options) <= 0 and default and default in options:
|
||||
selected_options.append(default)
|
||||
|
||||
printed_options = []
|
||||
for option in options:
|
||||
if option in selected_options:
|
||||
printed_options.append(f'>> {option}')
|
||||
else:
|
||||
printed_options.append(f'{option}')
|
||||
|
||||
section.clear(0, get_terminal_height()-section._cursor_y-1)
|
||||
x, y = print_large_list(printed_options, margin_bottom=2)
|
||||
section._cursor_y = len(printed_options)
|
||||
section._cursor_x = 0
|
||||
section.write_line(text)
|
||||
section.input_pos = section._cursor_x
|
||||
selected_option = section.get_keyboard_input(end=None)
|
||||
|
||||
if selected_option is None:
|
||||
if len(selected_options) <= 0 and default:
|
||||
selected_options = [default]
|
||||
|
||||
if len(selected_options) or allow_empty is True:
|
||||
break
|
||||
else:
|
||||
log('* Need to select at least one option!', fg='red')
|
||||
continue
|
||||
|
||||
elif selected_option.isdigit():
|
||||
if (selected_option := int(selected_option)) >= len(options):
|
||||
log('* Option is out of range, please select another one!', fg='red')
|
||||
continue
|
||||
selected_option = options[selected_option]
|
||||
if selected_option in selected_options:
|
||||
selected_options.remove(selected_option)
|
||||
else:
|
||||
selected_options.append(selected_option)
|
||||
|
||||
return selected_options
|
||||
|
||||
|
||||
class MiniCurses():
|
||||
def __init__(self, width, height):
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
self._cursor_y = 0
|
||||
self._cursor_x = 0
|
||||
|
||||
self.input_pos = 0
|
||||
|
||||
def write_line(self, text, clear_line=True):
|
||||
if clear_line:
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write("\033[%dG" % 0)
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write(" " * (get_terminal_width()-1))
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write("\033[%dG" % 0)
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write(text)
|
||||
sys.stdout.flush()
|
||||
self._cursor_x += len(text)
|
||||
|
||||
def clear(self, x, y):
|
||||
if x < 0: x = 0
|
||||
if y < 0: y = 0
|
||||
|
||||
#import time
|
||||
#sys.stdout.write(f"Clearing from: {x, y}")
|
||||
#sys.stdout.flush()
|
||||
#time.sleep(2)
|
||||
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write('\033[%d;%df' % (y, x))
|
||||
for line in range(get_terminal_height()-y-1, y):
|
||||
sys.stdout.write(" " * (get_terminal_width()-1))
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write('\033[%d;%df' % (y, x))
|
||||
sys.stdout.flush()
|
||||
|
||||
def deal_with_control_characters(self, char):
|
||||
mapper = {
|
||||
'\x7f' : 'BACKSPACE',
|
||||
'\r' : 'CR',
|
||||
'\n' : 'NL'
|
||||
}
|
||||
|
||||
if (mapped_char := mapper.get(char, None)) == 'BACKSPACE':
|
||||
if self._cursor_x <= self.input_pos:
|
||||
# Don't backspace futher back than the cursor start position during input
|
||||
return True
|
||||
# Move back to the current known position (BACKSPACE doesn't updated x-pos)
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write("\033[%dG" % (self._cursor_x))
|
||||
sys.stdout.flush()
|
||||
|
||||
# Write a blank space
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write(" ")
|
||||
sys.stdout.flush()
|
||||
|
||||
# And move back again
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write("\033[%dG" % (self._cursor_x))
|
||||
sys.stdout.flush()
|
||||
|
||||
self._cursor_x -= 1
|
||||
|
||||
return True
|
||||
elif mapped_char in ('CR', 'NL'):
|
||||
return True
|
||||
|
||||
return None
|
||||
|
||||
def get_keyboard_input(self, strip_rowbreaks=True, end='\n'):
|
||||
assert end in ['\r', '\n', None]
|
||||
|
||||
poller = select.epoll()
|
||||
response = ''
|
||||
|
||||
sys_fileno = sys.stdin.fileno()
|
||||
old_settings = termios.tcgetattr(sys_fileno)
|
||||
tty.setraw(sys_fileno)
|
||||
|
||||
poller.register(sys.stdin.fileno(), select.EPOLLIN)
|
||||
|
||||
EOF = False
|
||||
while EOF is False:
|
||||
for fileno, event in poller.poll(0.025):
|
||||
char = sys.stdin.read(1)
|
||||
|
||||
#sys.stdout.write(f"{[char]}")
|
||||
#sys.stdout.flush()
|
||||
|
||||
if (newline := (char in ('\n', '\r'))):
|
||||
EOF = True
|
||||
|
||||
if not newline or strip_rowbreaks is False:
|
||||
response += char
|
||||
|
||||
if self.deal_with_control_characters(char) is not True:
|
||||
self.write_line(response[-1], clear_line=False)
|
||||
|
||||
termios.tcsetattr(sys_fileno, termios.TCSADRAIN, old_settings)
|
||||
|
||||
if end:
|
||||
sys.stdout.write(end)
|
||||
sys.stdout.flush()
|
||||
self._cursor_x = 0
|
||||
self._cursor_y += 1
|
||||
|
||||
if response:
|
||||
return response
|
||||
|
||||
def ask_for_superuser_account(prompt='Username for required superuser with sudo privileges: ', forced=False):
|
||||
while 1:
|
||||
new_user = input(prompt).strip(' ')
|
||||
|
||||
if not new_user and forced:
|
||||
# TODO: make this text more generic?
|
||||
# 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!', fg='red')
|
||||
log(' * Since root is disabled, you need to create a least one superuser!', fg='red')
|
||||
continue
|
||||
elif not new_user and not forced:
|
||||
raise UserError("No superuser was created.")
|
||||
|
|
@ -112,7 +282,7 @@ def ask_for_superuser_account(prompt='Create a required super-user with sudo pri
|
|||
|
||||
def ask_for_additional_users(prompt='Any additional users to install (leave blank for no users): '):
|
||||
users = {}
|
||||
super_users = {}
|
||||
superusers = {}
|
||||
|
||||
while 1:
|
||||
new_user = input(prompt).strip(' ')
|
||||
|
|
@ -122,26 +292,37 @@ def ask_for_additional_users(prompt='Any additional users to install (leave blan
|
|||
continue
|
||||
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'):
|
||||
super_users[new_user] = {"!password" : password}
|
||||
if input("Should this user be a superuser (sudoer) [y/N]: ").strip(' ').lower() in ('y', 'yes'):
|
||||
superusers[new_user] = {"!password" : password}
|
||||
else:
|
||||
users[new_user] = {"!password" : password}
|
||||
|
||||
return users, super_users
|
||||
return users, superusers
|
||||
|
||||
def ask_for_a_timezone():
|
||||
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():
|
||||
return timezone
|
||||
while True:
|
||||
timezone = input('Enter a valid timezone (examples: Europe/Stockholm, US/Eastern) or press enter to use UTC: ').strip().strip('*.')
|
||||
if timezone == '':
|
||||
timezone = 'UTC'
|
||||
if (pathlib.Path("/usr")/"share"/"zoneinfo"/timezone).exists():
|
||||
return timezone
|
||||
else:
|
||||
log(
|
||||
f"Specified timezone {timezone} does not exist.",
|
||||
level=logging.WARNING,
|
||||
fg='red'
|
||||
)
|
||||
|
||||
def ask_for_bootloader() -> str:
|
||||
bootloader = "systemd-bootctl"
|
||||
if hasUEFI()==False:
|
||||
bootloader="grub-install"
|
||||
else:
|
||||
log(
|
||||
f"Time zone {timezone} does not exist, continuing with system default.",
|
||||
level=LOG_LEVELS.Warning,
|
||||
fg='red'
|
||||
)
|
||||
|
||||
bootloader_choice = input("Would you like to use GRUB as a bootloader instead of systemd-boot? [y/N] ").lower()
|
||||
if bootloader_choice == "y":
|
||||
bootloader="grub-install"
|
||||
return bootloader
|
||||
|
||||
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()
|
||||
|
|
@ -154,27 +335,56 @@ def ask_to_configure_network():
|
|||
# Optionally configure one network interface.
|
||||
#while 1:
|
||||
# {MAC: Ifname}
|
||||
interfaces = {'ISO-CONFIG' : 'Copy ISO network configuration to installation','NetworkManager':'Use NetworkManager to control and manage your internet connection', **list_interfaces()}
|
||||
interfaces = {
|
||||
'ISO-CONFIG' : 'Copy ISO network configuration to installation',
|
||||
'NetworkManager':'Use NetworkManager to control and manage your internet connection',
|
||||
**list_interfaces()
|
||||
}
|
||||
|
||||
nic = generic_select(interfaces.values(), "Select one network interface to configure (leave blank to skip): ")
|
||||
nic = generic_select(interfaces, "Select one network interface to configure (leave blank to skip): ")
|
||||
if nic and nic != 'Copy ISO network configuration to installation':
|
||||
if nic == 'Use NetworkManager to control and manage your internet connection':
|
||||
return {'nic': nic,'NetworkManager':True}
|
||||
mode = generic_select(['DHCP (auto detect)', 'IP (static)'], f"Select which mode to configure for {nic}: ")
|
||||
if mode == 'IP (static)':
|
||||
|
||||
# Current workaround:
|
||||
# For selecting modes without entering text within brackets,
|
||||
# printing out this part separate from options, passed in
|
||||
# `generic_select`
|
||||
modes = ['DHCP (auto detect)', 'IP (static)']
|
||||
for index, mode in enumerate(modes):
|
||||
print(f"{index}: {mode}")
|
||||
|
||||
mode = generic_select(['DHCP', 'IP'], f"Select which mode to configure for {nic} or leave blank for DHCP: ",
|
||||
options_output=False)
|
||||
if mode == 'IP':
|
||||
while 1:
|
||||
ip = input(f"Enter the IP and subnet for {nic} (example: 192.168.0.5/24): ").strip()
|
||||
if ip:
|
||||
# Implemented new check for correct IP/subnet input
|
||||
try:
|
||||
ipaddress.ip_interface(ip)
|
||||
break
|
||||
else:
|
||||
except ValueError:
|
||||
log(
|
||||
"You need to enter a valid IP in IP-config mode.",
|
||||
level=LOG_LEVELS.Warning,
|
||||
level=logging.WARNING,
|
||||
fg='red'
|
||||
)
|
||||
|
||||
if not len(gateway := input('Enter your gateway (router) IP address or leave blank for none: ').strip()):
|
||||
gateway = None
|
||||
# Implemented new check for correct gateway IP address
|
||||
while 1:
|
||||
gateway = input('Enter your gateway (router) IP address or leave blank for none: ').strip()
|
||||
try:
|
||||
if len(gateway) == 0:
|
||||
gateway = None
|
||||
else:
|
||||
ipaddress.ip_address(gateway)
|
||||
break
|
||||
except ValueError:
|
||||
log(
|
||||
"You need to enter a valid gateway (router) IP address.",
|
||||
level=logging.WARNING,
|
||||
fg='red'
|
||||
)
|
||||
|
||||
dns = None
|
||||
if len(dns_input := input('Enter your DNS servers (space separated, blank for none): ').strip()):
|
||||
|
|
@ -190,12 +400,13 @@ def ask_to_configure_network():
|
|||
|
||||
def ask_for_disk_layout():
|
||||
options = {
|
||||
'keep-existing' : 'Keep existing partition layout and select which ones to use where.',
|
||||
'format-all' : 'Format entire drive and setup a basic partition scheme.',
|
||||
'abort' : 'Abort the installation.'
|
||||
'keep-existing' : 'Keep existing partition layout and select which ones to use where',
|
||||
'format-all' : 'Format entire drive and setup a basic partition scheme',
|
||||
'abort' : 'Abort the installation'
|
||||
}
|
||||
|
||||
value = generic_select(options.values(), "Found partitions on the selected drive, (select by number) what you want to do: ")
|
||||
value = generic_select(options, "Found partitions on the selected drive, (select by number) what you want to do: ",
|
||||
allow_empty_input=False, sort=True)
|
||||
return next((key for key, val in options.items() if val == value), None)
|
||||
|
||||
def ask_for_main_filesystem_format():
|
||||
|
|
@ -206,40 +417,71 @@ def ask_for_main_filesystem_format():
|
|||
'f2fs' : 'f2fs'
|
||||
}
|
||||
|
||||
value = generic_select(options.values(), "Select which filesystem your main partition should use (by number or name): ")
|
||||
value = generic_select(options, "Select which filesystem your main partition should use (by number or name): ",
|
||||
allow_empty_input=False)
|
||||
return next((key for key, val in options.items() if val == value), None)
|
||||
|
||||
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: ", allow_empty_input=True, options_output=True, sort=False):
|
||||
"""
|
||||
A generic select function that does not output anything
|
||||
other than the options and their indexes. As an example:
|
||||
|
||||
generic_select(["first", "second", "third option"])
|
||||
1: first
|
||||
2: second
|
||||
3: third option
|
||||
0: first
|
||||
1: second
|
||||
2: third option
|
||||
|
||||
When the user has entered the option correctly,
|
||||
this function returns an item from list, a string, or None
|
||||
"""
|
||||
|
||||
if type(options) == dict: options = list(options)
|
||||
if sort: options = sorted(list(options))
|
||||
if len(options) <= 0: raise RequirementError('generic_select() requires at least one option to operate.')
|
||||
|
||||
for index, option in enumerate(options):
|
||||
print(f"{index}: {option}")
|
||||
|
||||
selected_option = input(input_text)
|
||||
if len(selected_option.strip()) <= 0:
|
||||
return None
|
||||
elif selected_option.isdigit():
|
||||
selected_option = int(selected_option)
|
||||
if selected_option > len(options):
|
||||
raise RequirementError(f'Selected option "{selected_option}" is out of range')
|
||||
selected_option = options[selected_option]
|
||||
elif selected_option in options:
|
||||
pass # We gave a correct absolute value
|
||||
else:
|
||||
raise RequirementError(f'Selected option "{selected_option}" does not exist in available options: {options}')
|
||||
# Checking if options are different from `list` or `dict`
|
||||
if type(options) not in [list, dict]:
|
||||
log(f" * Generic select doesn't support ({type(options)}) as type of options * ", fg='red')
|
||||
log(" * If problem persists, please create an issue on https://github.com/archlinux/archinstall/issues * ", fg='yellow')
|
||||
raise RequirementError("generic_select() requires list or dictionary as options.")
|
||||
# To allow only `list` and `dict`, converting values of options here.
|
||||
# Therefore, now we can only provide the dictionary itself
|
||||
if type(options) == dict: options = list(options.values())
|
||||
if sort: options = sorted(options) # As we pass only list and dict (converted to list), we can skip converting to list
|
||||
if len(options) == 0:
|
||||
log(f" * Generic select didn't find any options to choose from * ", fg='red')
|
||||
log(" * If problem persists, please create an issue on https://github.com/archlinux/archinstall/issues * ", fg='yellow')
|
||||
raise RequirementError('generic_select() requires at least one option to proceed.')
|
||||
|
||||
|
||||
# Added ability to disable the output of options items,
|
||||
# if another function displays something different from this
|
||||
if options_output:
|
||||
for index, option in enumerate(options):
|
||||
print(f"{index}: {option}")
|
||||
|
||||
# The new changes introduce a single while loop for all inputs processed by this function
|
||||
# Now the try...except block handles validation for invalid input from the user
|
||||
while True:
|
||||
try:
|
||||
selected_option = input(input_text)
|
||||
if len(selected_option.strip()) == 0:
|
||||
# `allow_empty_input` parameter handles return of None on empty input, if necessary
|
||||
# Otherwise raise `RequirementError`
|
||||
if allow_empty_input:
|
||||
return None
|
||||
raise RequirementError('Please select an option to continue')
|
||||
# Replaced `isdigit` with` isnumeric` to discard all negative numbers
|
||||
elif selected_option.isnumeric():
|
||||
selected_option = int(selected_option)
|
||||
if selected_option >= len(options):
|
||||
raise RequirementError(f'Selected option "{selected_option}" is out of range')
|
||||
selected_option = options[selected_option]
|
||||
break
|
||||
elif selected_option in options:
|
||||
break # We gave a correct absolute value
|
||||
else:
|
||||
raise RequirementError(f'Selected option "{selected_option}" does not exist in available options')
|
||||
except RequirementError as err:
|
||||
log(f" * {err} * ", fg='red')
|
||||
continue
|
||||
|
||||
return selected_option
|
||||
|
||||
def select_disk(dict_o_disks):
|
||||
|
|
@ -257,18 +499,14 @@ def select_disk(dict_o_disks):
|
|||
if len(drives) >= 1:
|
||||
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']})")
|
||||
drive = input('Select one of the above disks (by number or full path) or write /mnt to skip partitioning: ')
|
||||
if drive.strip() == '/mnt':
|
||||
return None
|
||||
elif drive.isdigit():
|
||||
drive = int(drive)
|
||||
if drive >= len(drives):
|
||||
raise DiskError(f'Selected option "{drive}" is out of range')
|
||||
drive = dict_o_disks[drives[drive]]
|
||||
elif drive in dict_o_disks:
|
||||
drive = dict_o_disks[drive]
|
||||
else:
|
||||
raise DiskError(f'Selected drive does not exist: "{drive}"')
|
||||
|
||||
log(f"You can skip selecting a drive and partitioning and use whatever drive-setup is mounted at /mnt (experimental)", fg="yellow")
|
||||
drive = generic_select(drives, 'Select one of the above disks (by name or number) or leave blank to use /mnt: ',
|
||||
options_output=False)
|
||||
if not drive:
|
||||
return drive
|
||||
|
||||
drive = dict_o_disks[drive]
|
||||
return drive
|
||||
|
||||
raise DiskError('select_disk() requires a non-empty dictionary of disks to select from.')
|
||||
|
|
@ -293,29 +531,21 @@ def select_profile(options):
|
|||
print(' -- The above list is a set of pre-programmed profiles. --')
|
||||
print(' -- They might make it easier to install things like desktop environments. --')
|
||||
print(' -- (Leave blank and hit enter to skip this step and continue) --')
|
||||
selected_profile = input('Enter a pre-programmed profile name if you want to install one: ')
|
||||
|
||||
if len(selected_profile.strip()) <= 0:
|
||||
return None
|
||||
|
||||
if selected_profile.isdigit() and (pos := int(selected_profile)) <= len(profiles)-1:
|
||||
selected_profile = profiles[pos]
|
||||
elif selected_profile in options:
|
||||
selected_profile = options[options.index(selected_profile)]
|
||||
else:
|
||||
RequirementError("Selected profile does not exist.")
|
||||
|
||||
return Profile(None, selected_profile)
|
||||
|
||||
raise RequirementError("Selecting profiles require a least one profile to be given as an option.")
|
||||
selected_profile = generic_select(profiles, 'Enter a pre-programmed profile name if you want to install one: ',
|
||||
options_output=False)
|
||||
if selected_profile:
|
||||
return Profile(None, selected_profile)
|
||||
else:
|
||||
raise RequirementError("Selecting profiles require a least one profile to be given as an option.")
|
||||
|
||||
def select_language(options, show_only_country_codes=True):
|
||||
"""
|
||||
Asks the user to select a language from the `options` dictionary parameter.
|
||||
Usually this is combined with :ref:`archinstall.list_keyboard_languages`.
|
||||
|
||||
:param options: A `dict` where keys are the language name, value should be a dict containing language information.
|
||||
:type options: dict
|
||||
:param options: A `generator` or `list` where keys are the language name, value should be a dict containing language information.
|
||||
:type options: generator or list
|
||||
|
||||
:param show_only_country_codes: Filters out languages that are not len(lang) == 2. This to limit the number of results from stuff like dvorak and x-latin1 alternatives.
|
||||
:type show_only_country_codes: bool
|
||||
|
|
@ -334,35 +564,37 @@ def select_language(options, show_only_country_codes=True):
|
|||
for index, language in enumerate(languages):
|
||||
print(f"{index}: {language}")
|
||||
|
||||
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): ')
|
||||
|
||||
if len(selected_language.strip()) == 0:
|
||||
return DEFAULT_KEYBOARD_LANGUAGE
|
||||
elif selected_language.lower() in ('?', 'help'):
|
||||
while True:
|
||||
filter_string = input('Search for layout containing (example: "sv-"): ')
|
||||
new_options = list(search_keyboard_layout(filter_string))
|
||||
print(" -- You can choose a layout that isn't in this list, but whose name you know --")
|
||||
print(" -- Also, you can enter '?' or 'help' to search for more languages, or skip to use US layout --")
|
||||
|
||||
if len(new_options) <= 0:
|
||||
log(f"Search string '{filter_string}' yielded no results, please try another search or Ctrl+D to abort.", fg='yellow')
|
||||
while True:
|
||||
selected_language = input('Select one of the above keyboard languages (by name or full name): ')
|
||||
if not selected_language:
|
||||
return DEFAULT_KEYBOARD_LANGUAGE
|
||||
elif selected_language.lower() in ('?', 'help'):
|
||||
while True:
|
||||
filter_string = input("Search for layout containing (example: \"sv-\") or enter 'exit' to exit from search: ")
|
||||
|
||||
if filter_string.lower() == 'exit':
|
||||
return select_language(list_keyboard_languages())
|
||||
|
||||
new_options = list(search_keyboard_layout(filter_string))
|
||||
|
||||
if len(new_options) <= 0:
|
||||
log(f"Search string '{filter_string}' yielded no results, please try another search.", fg='yellow')
|
||||
continue
|
||||
|
||||
return select_language(new_options, show_only_country_codes=False)
|
||||
elif selected_language.isnumeric():
|
||||
selected_language = int(selected_language)
|
||||
if selected_language >= len(languages):
|
||||
log(' * Selected option is out of range * ', fg='red')
|
||||
continue
|
||||
|
||||
return select_language(new_options, show_only_country_codes=False)
|
||||
|
||||
elif selected_language.isdigit() and (pos := int(selected_language)) <= len(languages)-1:
|
||||
selected_language = languages[pos]
|
||||
return selected_language
|
||||
# I'm leaving "options" on purpose here.
|
||||
# Since languages possibly contains a filtered version of
|
||||
# all possible language layouts, and we might want to write
|
||||
# for instance sv-latin1 (if we know that exists) without having to
|
||||
# go through the search step.
|
||||
elif selected_language in options:
|
||||
selected_language = options[options.index(selected_language)]
|
||||
return selected_language
|
||||
else:
|
||||
raise RequirementError("Selected language does not exist.")
|
||||
return languages[selected_language]
|
||||
elif verify_keyboard_layout(selected_language):
|
||||
return selected_language
|
||||
else:
|
||||
log(" * Given language wasn't found * ", fg='red')
|
||||
|
||||
raise RequirementError("Selecting languages require a least one language to be given as an option.")
|
||||
|
||||
|
|
@ -389,23 +621,76 @@ def select_mirror_regions(mirrors, show_top_mirrors=True):
|
|||
print_large_list(regions, margin_bottom=4)
|
||||
|
||||
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): ')
|
||||
if len(selected_mirror.strip()) == 0:
|
||||
selected_mirror = generic_select(regions, 'Select one of the above regions to download packages from (by number or full name): ',
|
||||
options_output=False)
|
||||
if not selected_mirror:
|
||||
# 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 {}
|
||||
|
||||
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)]
|
||||
selected_mirrors[region] = mirrors[region]
|
||||
elif selected_mirror in mirrors:
|
||||
selected_mirrors[selected_mirror] = mirrors[selected_mirror]
|
||||
else:
|
||||
raise RequirementError("Selected region does not exist.")
|
||||
# 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.
|
||||
|
||||
selected_mirrors[selected_mirror] = mirrors[selected_mirror]
|
||||
return selected_mirrors
|
||||
|
||||
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)
|
||||
"""
|
||||
|
||||
drivers = sorted(list(options))
|
||||
|
||||
if drivers:
|
||||
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 **')
|
||||
|
||||
initial_option = generic_select(drivers, input_text="Select your graphics card driver: ")
|
||||
selected_driver = options[initial_option]
|
||||
|
||||
if type(selected_driver) == dict:
|
||||
driver_options = sorted(list(selected_driver))
|
||||
|
||||
driver_package_group = generic_select(driver_options, f'Which driver-type do you want for {initial_option}: ',
|
||||
allow_empty_input=False)
|
||||
driver_package_group = selected_driver[driver_package_group]
|
||||
|
||||
return driver_package_group
|
||||
|
||||
return selected_driver
|
||||
|
||||
raise RequirementError("Selecting drivers require a least one profile to be given as an option.")
|
||||
|
||||
def select_kernel(options):
|
||||
"""
|
||||
Asks the user to select a kernel for system.
|
||||
|
||||
:param options: A `list` with kernel options
|
||||
:type options: list
|
||||
|
||||
:return: The string as a selected kernel
|
||||
:rtype: string
|
||||
"""
|
||||
|
||||
DEFAULT_KERNEL = "linux"
|
||||
|
||||
kernels = sorted(list(options))
|
||||
|
||||
if kernels:
|
||||
return generic_multi_select(kernels, f"Choose which kernel to use (leave blank for default: {DEFAULT_KERNEL}): ", default=DEFAULT_KERNEL)
|
||||
|
||||
raise RequirementError("Selecting kernels require a least one kernel to be given as an option.")
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Packages
|
|||
========
|
||||
|
||||
.. autofunction:: archinstall.find_package
|
||||
|
||||
Be
|
||||
.. autofunction:: archinstall.find_packages
|
||||
|
||||
Locale related
|
||||
|
|
|
|||
|
|
@ -2,18 +2,8 @@
|
|||
|
||||
# New features *(v2.2.0)*
|
||||
|
||||
Merge new features in to `torxed-v2.2.0`.<br>
|
||||
This branch is designated for potential breaking changes, added complexity and new functionality.
|
||||
|
||||
# Bug fixes *(v2.1.4)*
|
||||
|
||||
Merge against `master` for bug fixes and anything that improves stability and quality of life.<br>
|
||||
This excludes:
|
||||
* New functionality
|
||||
* Added complexity
|
||||
* Breaking changes
|
||||
|
||||
Any changes to `master` automatically gets pulled in to `torxed-v2.2.0` to avoid merge hell.
|
||||
All future work towards *`v2.2.0`* is done against `master` now.<br>
|
||||
Any patch work to existing verions will have to create a new branch against the tagged versions.
|
||||
|
||||
# Describe your PR
|
||||
|
||||
|
|
@ -23,6 +13,4 @@ If the PR is larger than ~20 lines, please describe it here unless described in
|
|||
# Testing
|
||||
|
||||
Any new feature or stability improvement should be tested if possible.
|
||||
Please follow the test instructions at the bottom of the README.
|
||||
|
||||
*These PR guidelines will change after 2021-05-01, which is when `v2.1.4` gets onto the new ISO*
|
||||
Please follow the test instructions at the bottom of the README or use the ISO built on each PR.
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import getpass, time, json, os
|
||||
import getpass, time, json, os, logging
|
||||
import archinstall
|
||||
from archinstall.lib.hardware import hasUEFI
|
||||
from archinstall.lib.profiles import Profile
|
||||
|
||||
if hasUEFI() is False:
|
||||
archinstall.log("ArchInstall currently only supports machines booted with UEFI.\nMBR & GRUB support is coming in version 2.2.0!", fg="red", level=archinstall.LOG_LEVELS.Error)
|
||||
exit(1)
|
||||
if archinstall.arguments.get('help'):
|
||||
print("See `man archinstall` for help.")
|
||||
exit(0)
|
||||
|
||||
# For support reasons, we'll log the disk layout pre installation to match against post-installation layout
|
||||
archinstall.log(f"Disk states before installing: {archinstall.disk_layouts()}", level=archinstall.LOG_LEVELS.Debug)
|
||||
|
|
@ -67,6 +67,7 @@ def ask_user_questions():
|
|||
partition_mountpoints[partition] = None
|
||||
except archinstall.UnknownFilesystemFormat as err:
|
||||
archinstall.log(f" {partition} (Filesystem not supported)", fg='red')
|
||||
|
||||
|
||||
# We then ask what to do with the partitions.
|
||||
if (option := archinstall.ask_for_disk_layout()) == 'abort':
|
||||
|
|
@ -77,12 +78,18 @@ def ask_user_questions():
|
|||
|
||||
archinstall.log(f" ** You will now select which partitions to use by selecting mount points (inside the installation). **")
|
||||
archinstall.log(f" ** The root would be a simple / and the boot partition /boot (as all paths are relative inside the installation). **")
|
||||
mountpoints_set = []
|
||||
while True:
|
||||
# Select a partition
|
||||
partition = archinstall.generic_select(partition_mountpoints.keys(),
|
||||
# If we provide keys as options, it's better to convert them to list and sort before passing
|
||||
mountpoints_list = sorted(list(partition_mountpoints.keys()))
|
||||
partition = archinstall.generic_select(mountpoints_list,
|
||||
"Select a partition by number that you want to set a mount-point for (leave blank when done): ")
|
||||
if not partition:
|
||||
break
|
||||
if set(mountpoints_set) & {'/', '/boot'} == {'/', '/boot'}:
|
||||
break
|
||||
|
||||
continue
|
||||
|
||||
# Select a mount-point
|
||||
mountpoint = input(f"Enter a mount-point for {partition}: ").strip(' ')
|
||||
|
|
@ -109,7 +116,7 @@ def ask_user_questions():
|
|||
# we have to check if we support it. We can do this by formatting /dev/null with the partitions filesystem.
|
||||
# There's a nice wrapper for this on the partition object itself that supports a path-override during .format()
|
||||
try:
|
||||
partition.format(new_filesystem, path='/dev/null', log_formating=False, allow_formatting=True)
|
||||
partition.format(new_filesystem, path='/dev/null', log_formatting=False, allow_formatting=True)
|
||||
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/archlinux/archinstall/issues.")
|
||||
archinstall.log(f"Until then, please enter another supported filesystem.")
|
||||
|
|
@ -123,6 +130,7 @@ def ask_user_questions():
|
|||
# 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
|
||||
# set if we actually want to format it anyway.
|
||||
mountpoints_set.append(mountpoint)
|
||||
partition.allow_formatting = True
|
||||
partition.target_mountpoint = mountpoint
|
||||
# Only overwrite the filesystem definition if we selected one:
|
||||
|
|
@ -131,12 +139,14 @@ def ask_user_questions():
|
|||
|
||||
archinstall.log('Using existing partition table reported above.')
|
||||
elif option == 'format-all':
|
||||
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
||||
if not archinstall.arguments.get('filesystem', None):
|
||||
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
||||
archinstall.arguments['harddrive'].keep_partitions = False
|
||||
elif archinstall.arguments['harddrive']:
|
||||
# If the drive doesn't have any partitions, safely mark the disk with keep_partitions = False
|
||||
# and ask the user for a root filesystem.
|
||||
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
||||
if not archinstall.arguments.get('filesystem', None):
|
||||
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
||||
archinstall.arguments['harddrive'].keep_partitions = False
|
||||
|
||||
# Get disk encryption password (or skip if blank)
|
||||
|
|
@ -144,7 +154,7 @@ def ask_user_questions():
|
|||
if (passwd := archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ')):
|
||||
archinstall.arguments['!encryption-password'] = passwd
|
||||
archinstall.arguments['harddrive'].encryption_password = archinstall.arguments['!encryption-password']
|
||||
|
||||
archinstall.arguments["bootloader"] = archinstall.ask_for_bootloader()
|
||||
# Get the hostname for the machine
|
||||
if not archinstall.arguments.get('hostname', None):
|
||||
archinstall.arguments['hostname'] = input('Desired hostname for the installation: ').strip(' ')
|
||||
|
|
@ -165,7 +175,7 @@ def ask_user_questions():
|
|||
|
||||
# Ask for archinstall-specific profiles (such as desktop environments etc)
|
||||
if not archinstall.arguments.get('profile', None):
|
||||
archinstall.arguments['profile'] = archinstall.select_profile(filter(lambda profile: (Profile(None, profile).is_top_level_profile()), archinstall.list_profiles()))
|
||||
archinstall.arguments['profile'] = archinstall.select_profile(archinstall.list_profiles(filter_top_level_profiles=True))
|
||||
else:
|
||||
archinstall.arguments['profile'] = archinstall.list_profiles()[archinstall.arguments['profile']]
|
||||
|
||||
|
|
@ -181,7 +191,6 @@ def ask_user_questions():
|
|||
|
||||
# Ask about audio server selection if one is not already set
|
||||
if not archinstall.arguments.get('audio', None):
|
||||
|
||||
# only ask for audio server selection on a desktop profile
|
||||
if str(archinstall.arguments['profile']) == 'Profile(desktop)':
|
||||
archinstall.arguments['audio'] = archinstall.ask_for_audio_selection()
|
||||
|
|
@ -190,11 +199,16 @@ def ask_user_questions():
|
|||
# we will not try to remove packages post-installation to not have audio, as that may cause multiple issues
|
||||
archinstall.arguments['audio'] = None
|
||||
|
||||
# Ask for preferred kernel:
|
||||
if not archinstall.arguments.get("kernels", None):
|
||||
kernels = ["linux", "linux-lts", "linux-zen", "linux-hardened"]
|
||||
archinstall.arguments['kernels'] = archinstall.select_kernel(kernels)
|
||||
|
||||
# Additional packages (with some light weight error handling for invalid package names)
|
||||
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.")
|
||||
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']):
|
||||
|
|
@ -223,8 +237,8 @@ def ask_user_questions():
|
|||
def perform_installation_steps():
|
||||
print()
|
||||
print('This is your chosen configuration:')
|
||||
archinstall.log("-- Guided template chosen (with below config) --", level=archinstall.LOG_LEVELS.Debug)
|
||||
archinstall.log(json.dumps(archinstall.arguments, indent=4, sort_keys=True, cls=archinstall.JSON), level=archinstall.LOG_LEVELS.Info)
|
||||
archinstall.log("-- Guided template chosen (with below config) --", level=logging.DEBUG)
|
||||
archinstall.log(json.dumps(archinstall.arguments, indent=4, sort_keys=True, cls=archinstall.JSON), level=logging.INFO)
|
||||
print()
|
||||
|
||||
input('Press Enter to continue.')
|
||||
|
|
@ -242,7 +256,11 @@ def perform_installation_steps():
|
|||
Setup the blockdevice, filesystem (and optionally encryption).
|
||||
Once that's done, we'll hand over to perform_installation()
|
||||
"""
|
||||
with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs:
|
||||
mode = archinstall.GPT
|
||||
if hasUEFI() is False:
|
||||
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'))
|
||||
|
|
@ -264,9 +282,9 @@ def perform_installation_steps():
|
|||
else:
|
||||
partition.format()
|
||||
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('/boot').format('vfat')
|
||||
archinstall.log(f"Did not format {partition} because .safe_to_format() returned False or .allow_formatting was False.", level=logging.DEBUG)
|
||||
if hasUEFI():
|
||||
fs.find_partition('/boot').format('vfat')# we don't have a boot partition in bios mode
|
||||
|
||||
if archinstall.arguments.get('!encryption-password', None):
|
||||
# First encrypt and unlock, then format the desired partition inside the encrypted part.
|
||||
|
|
@ -278,8 +296,8 @@ def perform_installation_steps():
|
|||
else:
|
||||
fs.find_partition('/').format(fs.find_partition('/').filesystem)
|
||||
fs.find_partition('/').mount('/mnt')
|
||||
|
||||
fs.find_partition('/boot').mount('/mnt/boot')
|
||||
if hasUEFI():
|
||||
fs.find_partition('/boot').mount('/mnt/boot')
|
||||
|
||||
perform_installation('/mnt')
|
||||
|
||||
|
|
@ -290,25 +308,25 @@ def perform_installation(mountpoint):
|
|||
Only requirement is that the block devices are
|
||||
formatted and setup prior to entering this function.
|
||||
"""
|
||||
with archinstall.Installer(mountpoint) as installation:
|
||||
with archinstall.Installer(mountpoint, kernels=archinstall.arguments.get('kernels', 'linux')) as installation:
|
||||
## if len(mirrors):
|
||||
# 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
|
||||
# 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 (reflector) to complete.', level=archinstall.LOG_LEVELS.Info)
|
||||
installation.log(f'Waiting for automatic mirror selection (reflector) to complete.', level=logging.INFO)
|
||||
while archinstall.service_state('reflector') not in ('dead', 'failed'):
|
||||
time.sleep(1)
|
||||
|
||||
# Set mirrors used by pacstrap (outside of installation)
|
||||
if archinstall.arguments.get('mirror-region', None):
|
||||
archinstall.use_mirrors(archinstall.arguments['mirror-region']) # Set the mirrors for the live medium
|
||||
|
||||
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
|
||||
if archinstall.arguments["bootloader"]=="grub-install" and hasUEFI()==True:
|
||||
installation.add_additional_packages("grub")
|
||||
installation.set_keyboard_language(archinstall.arguments['keyboard-language'])
|
||||
installation.add_bootloader()
|
||||
installation.add_bootloader(archinstall.arguments["bootloader"])
|
||||
|
||||
# If user selected to copy the current ISO network configuration
|
||||
# Perform a copy of the config
|
||||
|
|
@ -324,15 +342,16 @@ def perform_installation(mountpoint):
|
|||
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)
|
||||
installation.log(f"This audio server will be used: {archinstall.arguments.get('audio', None)}", level=logging.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)
|
||||
installation.log("No audio server will be installed.", level=logging.INFO)
|
||||
|
||||
if archinstall.arguments.get('packages', None) and archinstall.arguments.get('packages', None)[0] != '':
|
||||
installation.add_additional_packages(archinstall.arguments.get('packages', None))
|
||||
|
|
@ -342,7 +361,7 @@ def perform_installation(mountpoint):
|
|||
|
||||
for user, user_info in archinstall.arguments.get('users', {}).items():
|
||||
installation.user_create(user, user_info["!password"], sudo=False)
|
||||
|
||||
|
||||
for superuser, user_info in archinstall.arguments.get('superusers', {}).items():
|
||||
installation.user_create(superuser, user_info["!password"], sudo=True)
|
||||
|
||||
|
|
@ -352,6 +371,15 @@ def perform_installation(mountpoint):
|
|||
if (root_pw := archinstall.arguments.get('!root-password', None)) and len(root_pw):
|
||||
installation.user_set_pw('root', root_pw)
|
||||
|
||||
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:
|
||||
if not imported._post_install():
|
||||
archinstall.log(
|
||||
' * Profile\'s post configuration requirements was not fulfilled.',
|
||||
fg='red'
|
||||
)
|
||||
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", ""):
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
import archinstall
|
||||
import json
|
||||
import urllib.request
|
||||
import git
|
||||
|
||||
__packages__ = ['nano', 'wget', 'git']
|
||||
|
||||
if __name__ == '52-54-00-12-34-56':
|
||||
awesome = archinstall.Application(installation, 'postgresql')
|
||||
awesome.install()
|
||||
|
||||
"""
|
||||
# Unmount and close previous runs (Mainly only used for re-runs, but won't hurt.)
|
||||
archinstall.sys_command(f'umount -R /mnt', suppress_errors=True)
|
||||
archinstall.sys_command(f'cryptsetup close /dev/mapper/luksloop', suppress_errors=True)
|
||||
|
|
@ -11,7 +17,7 @@ archinstall.sys_command(f'cryptsetup close /dev/mapper/luksloop', suppress_error
|
|||
harddrive = archinstall.all_disks()['/dev/sda']
|
||||
disk_password = '1234'
|
||||
|
||||
with archinstall.Filesystem(harddrive, archinstall.GPT) as fs:
|
||||
with archinstall.Filesystem(harddrive) as fs:
|
||||
# Use the entire disk instead of setting up partitions on your own
|
||||
fs.use_entire_disk('luks2')
|
||||
|
||||
|
|
@ -30,22 +36,19 @@ with archinstall.Filesystem(harddrive, archinstall.GPT) as fs:
|
|||
if installation.minimal_installation():
|
||||
installation.add_bootloader()
|
||||
|
||||
installation.add_additional_packages(['nano', 'wget', 'git'])
|
||||
installation.add_additional_packages(__packages__)
|
||||
installation.install_profile('awesome')
|
||||
|
||||
installation.user_create('anton', 'test')
|
||||
installation.user_create('devel', 'devel')
|
||||
installation.user_set_pw('root', 'toor')
|
||||
|
||||
repo = git.Repo('./')
|
||||
commit = repo.head.commit.hexsha[:7]
|
||||
|
||||
print(f'Submitting {commit}: success')
|
||||
print(f'Submitting {archinstall.__version__}: success')
|
||||
|
||||
conditions = {
|
||||
"project": "archinstall",
|
||||
"profile": "52-54-00-12-34-56",
|
||||
"status": "success",
|
||||
"commit": commit
|
||||
"version": archinstall.__version__
|
||||
}
|
||||
req = urllib.request.Request("https://api.archlinux.life/build/success",
|
||||
data=json.dumps(conditions).encode('utf8'),
|
||||
|
|
@ -53,4 +56,5 @@ with archinstall.Filesystem(harddrive, archinstall.GPT) as fs:
|
|||
try:
|
||||
urllib.request.urlopen(req, timeout=5)
|
||||
except:
|
||||
pass
|
||||
pass
|
||||
"""
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
import archinstall
|
||||
|
||||
installation.add_additional_packages("alacritty")
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import archinstall
|
||||
|
||||
__packages__ = ["awesome", "xorg-xrandr", "xterm", "feh", "slock", "terminus-font", "gnu-free-fonts", "ttf-liberation", "xsel"]
|
||||
|
||||
installation.install_profile('xorg')
|
||||
|
||||
installation.add_additional_packages(
|
||||
"awesome xorg-xrandr xterm feh slock terminus-font gnu-free-fonts ttf-liberation xsel"
|
||||
)
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
with open(f'{installation.target}/etc/X11/xinit/xinitrc', 'r') as xinitrc:
|
||||
xinitrc_data = xinitrc.read()
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
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,3 +0,0 @@
|
|||
import archinstall
|
||||
|
||||
installation.add_additional_packages("cinnamon system-config-printer gnome-keyring gnome-terminal blueberry metacity lightdm lightdm-gtk-greeter")
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import archinstall
|
||||
|
||||
# Define the package list in order for lib to source
|
||||
# which packages will be installed by this profile
|
||||
__packages__ = ["cockpit", "udisks2", "packagekit"]
|
||||
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
installation.enable_service('cockpit.socket')
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import archinstall
|
||||
|
||||
# Define the package list in order for lib to source
|
||||
# which packages will be installed by this profile
|
||||
__packages__ = ["docker"]
|
||||
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
installation.enable_service('docker')
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import archinstall
|
||||
|
||||
installation.add_additional_packages("gnome gnome-tweaks gdm")
|
||||
# Note: gdm should be part of the gnome group, but adding it here for clarity
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import archinstall
|
||||
|
||||
# Define the package list in order for lib to source
|
||||
# which packages will be installed by this profile
|
||||
__packages__ = ["apache"]
|
||||
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
installation.enable_service('httpd')
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
import archinstall
|
||||
installation.add_additional_packages("i3-gaps")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
import archinstall
|
||||
installation.add_additional_packages("i3-wm")
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import archinstall
|
||||
packages = "plasma-meta konsole kate dolphin sddm plasma-wayland-session"
|
||||
if "nvidia" in _gfx_driver_packages:
|
||||
packages = packages + " egl-wayland"
|
||||
installation.add_additional_packages(packages)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import archinstall
|
||||
|
||||
# Define the package list in order for lib to source
|
||||
# which packages will be installed by this profile
|
||||
__packages__ = ["lighttpd"]
|
||||
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
installation.enable_service('lighttpd')
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
import archinstall
|
||||
|
||||
installation.add_additional_packages("lxqt breeze-icons oxygen-icons xdg-utils ttf-freefont leafpad slock archlinux-wallpaper sddm")
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import archinstall
|
||||
|
||||
# Define the package list in order for lib to source
|
||||
# which packages will be installed by this profile
|
||||
__packages__ = ["mariadb"]
|
||||
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
installation.arch_chroot("mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql")
|
||||
|
||||
installation.enable_service('mariadb')
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
import archinstall
|
||||
|
||||
installation.add_additional_packages("mate mate-extra lightdm lightdm-gtk-greeter")
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import archinstall
|
||||
|
||||
# Define the package list in order for lib to source
|
||||
# which packages will be installed by this profile
|
||||
__packages__ = ["nginx"]
|
||||
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
installation.enable_service('nginx')
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import archinstall
|
||||
|
||||
# Define the package list in order for lib to source
|
||||
# which packages will be installed by this profile
|
||||
__packages__ = ["postgresql"]
|
||||
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
installation.arch_chroot("initdb -D /var/lib/postgres/data", runas='postgres')
|
||||
|
||||
installation.enable_service('postgresql')
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import archinstall
|
||||
|
||||
# Define the package list in order for lib to source
|
||||
# which packages will be installed by this profile
|
||||
__packages__ = ["openssh"]
|
||||
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
installation.enable_service('sshd')
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
import archinstall
|
||||
packages = "sway swaylock swayidle waybar dmenu light grim slurp pavucontrol alacritty"
|
||||
installation.add_additional_packages(packages)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import archinstall
|
||||
|
||||
# This is using Tomcat 10 as that is the latest release at the time of implementation.
|
||||
# This should probably be updated to use newer releases as they come out.
|
||||
|
||||
# Define the package list in order for lib to source
|
||||
# which packages will be installed by this profile
|
||||
__packages__ = ["tomcat10"]
|
||||
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
installation.enable_service('tomcat10')
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
import archinstall
|
||||
|
||||
installation.add_additional_packages("xfce4 xfce4-goodies lightdm lightdm-gtk-greeter")
|
||||
|
|
@ -6,7 +6,7 @@ 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']
|
||||
__packages__ = ['nemo', 'gpicview-gtk3', 'main', 'alacritty']
|
||||
|
||||
def _prep_function(*args, **kwargs):
|
||||
"""
|
||||
|
|
@ -35,9 +35,6 @@ if __name__ == 'awesome':
|
|||
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
alacritty = archinstall.Application(installation, 'alacritty')
|
||||
alacritty.install()
|
||||
|
||||
# TODO: Copy a full configuration to ~/.config/awesome/rc.lua instead.
|
||||
with open(f'{installation.target}/etc/xdg/awesome/rc.lua', 'r') as fh:
|
||||
awesome_lua = fh.read()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import archinstall
|
|||
|
||||
is_top_level_profile = False
|
||||
|
||||
# "It is recommended also to install the gnome group, which contains applications required for the standard GNOME experience." - Arch Wiki
|
||||
__packages__ = ["budgie-desktop", "lightdm", "lightdm-gtk-greeter", "gnome"]
|
||||
|
||||
def _prep_function(*args, **kwargs):
|
||||
"""
|
||||
Magic function called by the importing installer
|
||||
|
|
@ -27,8 +30,7 @@ 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()
|
||||
# Install the Budgie packages
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
installation.enable_service('lightdm') # Light Display Manager
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import archinstall
|
|||
|
||||
is_top_level_profile = False
|
||||
|
||||
__packages__ = ["cinnamon", "system-config-printer", "gnome-keyring", "gnome-terminal", "blueberry", "metacity", "lightdm", "lightdm-gtk-greeter"]
|
||||
|
||||
def _prep_function(*args, **kwargs):
|
||||
"""
|
||||
Magic function called by the importing installer
|
||||
|
|
@ -27,8 +29,7 @@ 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()
|
||||
# Install the Cinnamon packages
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
installation.enable_service('lightdm') # Light Display Manager
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
# A desktop environment using "Deepin".
|
||||
|
||||
import archinstall, os
|
||||
|
||||
is_top_level_profile = False
|
||||
|
||||
__packages__ = ["deepin", "deepin-terminal", "deepin-editor"]
|
||||
|
||||
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 Deepin packages
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
# Enable autostart of Deepin for all users
|
||||
installation.enable_service('lightdm')
|
||||
|
|
@ -17,7 +17,8 @@ def _prep_function(*args, **kwargs):
|
|||
"""
|
||||
|
||||
supported_desktops = ['gnome', 'kde', 'awesome', 'sway', 'cinnamon', 'xfce4', 'lxqt', 'i3', 'budgie', 'mate']
|
||||
desktop = archinstall.generic_select(supported_desktops, 'Select your desired desktop environment: ')
|
||||
desktop = archinstall.generic_select(supported_desktops, 'Select your desired desktop environment: ',
|
||||
allow_empty_input=False, sort=True)
|
||||
|
||||
# Temporarily store the selected desktop profile
|
||||
# in a session-safe location, since this module will get reloaded
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import archinstall
|
|||
|
||||
is_top_level_profile = False
|
||||
|
||||
# Note: GDM should be part of the gnome group, but adding it here for clarity
|
||||
__packages__ = ["gnome", "gnome-tweaks", "gdm"]
|
||||
|
||||
def _prep_function(*args, **kwargs):
|
||||
"""
|
||||
Magic function called by the importing installer
|
||||
|
|
@ -28,9 +31,8 @@ if __name__ == 'gnome':
|
|||
# Install dependency profiles
|
||||
installation.install_profile('xorg')
|
||||
|
||||
# Install the application gnome from the template under /applications/
|
||||
gnome = archinstall.Application(installation, 'gnome')
|
||||
gnome.install()
|
||||
# Install the GNOME packages
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
installation.enable_service('gdm') # Gnome Display Manager
|
||||
# We could also start it via xinitrc since we do have Xorg,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ 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']
|
||||
__packages__ = ['i3lock', 'i3status', 'i3blocks', 'xterm', 'lightdm-gtk-greeter', 'lightdm']
|
||||
|
||||
def _prep_function(*args, **kwargs):
|
||||
"""
|
||||
|
|
@ -17,7 +17,8 @@ def _prep_function(*args, **kwargs):
|
|||
"""
|
||||
|
||||
supported_configurations = ['i3-wm', 'i3-gaps']
|
||||
desktop = archinstall.generic_select(supported_configurations, 'Select your desired configuration: ')
|
||||
desktop = archinstall.generic_select(supported_configurations, 'Select your desired configuration: ',
|
||||
allow_empty_input=False, sort=True)
|
||||
|
||||
# Temporarily store the selected desktop profile
|
||||
# in a session-safe location, since this module will get reloaded
|
||||
|
|
@ -47,17 +48,16 @@ if __name__ == 'i3':
|
|||
"""
|
||||
|
||||
# Install common packages for all i3 configurations
|
||||
installation.add_additional_packages(__packages__)
|
||||
installation.add_additional_packages(__packages__[:4])
|
||||
|
||||
# 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")
|
||||
# gaps is installed by deafult so we are overriding it here with lightdm
|
||||
installation.add_additional_packages(__packages__[4:])
|
||||
|
||||
# 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()
|
||||
installation.add_additional_packages(archinstall.storage['_i3_configuration'])
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import archinstall, os
|
|||
|
||||
is_top_level_profile = False
|
||||
|
||||
__packages__ = ["plasma-meta", "konsole", "kate", "dolphin", "sddm", "plasma-wayland-session", "egl-wayland"]
|
||||
|
||||
# TODO: Remove hard dependency of bash (due to .bash_profile)
|
||||
|
||||
def _prep_function(*args, **kwargs):
|
||||
|
|
@ -37,9 +39,8 @@ if __name__ == 'kde':
|
|||
# Install dependency profiles
|
||||
installation.install_profile('xorg')
|
||||
|
||||
# Install the application kde from the template under /applications/
|
||||
kde = archinstall.Application(installation, 'kde')
|
||||
kde.install()
|
||||
# Install the KDE packages
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
# Enable autostart of KDE for all users
|
||||
installation.enable_service('sddm')
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import archinstall
|
|||
|
||||
is_top_level_profile = False
|
||||
|
||||
__packages__ = ["lxqt", "breeze-icons", "oxygen-icons", "xdg-utils", "ttf-freefont", "leafpad", "slock", "sddm"]
|
||||
|
||||
def _prep_function(*args, **kwargs):
|
||||
"""
|
||||
Magic function called by the importing installer
|
||||
|
|
@ -28,8 +30,7 @@ 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()
|
||||
# Install the LXQt packages
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
installation.enable_service('sddm') # SDDM Display Manager
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import archinstall
|
|||
|
||||
is_top_level_profile = False
|
||||
|
||||
__packages__ = ["mate", "mate-extra", "lightdm", "lightdm-gtk-greeter"]
|
||||
|
||||
def _prep_function(*args, **kwargs):
|
||||
"""
|
||||
Magic function called by the importing installer
|
||||
|
|
@ -27,8 +29,7 @@ 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()
|
||||
# Install the MATE packages
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
installation.enable_service('lightdm') # Light Display Manager
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
# Used to select various server application profiles on top of a minimal installation.
|
||||
|
||||
import archinstall, os, logging
|
||||
|
||||
is_top_level_profile = True
|
||||
|
||||
available_servers = ["cockpit", "docker", "httpd", "lighttpd", "mariadb", "nginx", "postgresql", "sshd", "tomcat"]
|
||||
|
||||
def _prep_function(*args, **kwargs):
|
||||
"""
|
||||
Magic function called by the importing installer
|
||||
before continuing any further.
|
||||
"""
|
||||
selected_servers = archinstall.generic_multi_select(available_servers, f"Choose which servers to install and enable (leave blank for a minimal installation): ")
|
||||
archinstall.storage['_selected_servers'] = selected_servers
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == 'server':
|
||||
"""
|
||||
This "profile" is a meta-profile.
|
||||
"""
|
||||
archinstall.log(f'Now installing the selected servers.', level=logging.INFO)
|
||||
archinstall.log(archinstall.storage['_selected_servers'], level=logging.DEBUG)
|
||||
for server in archinstall.storage['_selected_servers']:
|
||||
archinstall.log(f'Installing {server} ...', level=logging.INFO)
|
||||
app = archinstall.Application(installation, server)
|
||||
app.install()
|
||||
|
||||
archinstall.log('If your selections included multiple servers with the same port, you may have to reconfigure them.', fg="yellow", level=logging.INFO)
|
||||
|
|
@ -4,6 +4,8 @@ import archinstall
|
|||
|
||||
is_top_level_profile = False
|
||||
|
||||
__packages__ = ["sway", "swaylock", "swayidle", "waybar", "dmenu", "light", "grim", "slurp", "pavucontrol", "alacritty"]
|
||||
|
||||
def _prep_function(*args, **kwargs):
|
||||
"""
|
||||
Magic function called by the importing installer
|
||||
|
|
@ -11,12 +13,18 @@ def _prep_function(*args, **kwargs):
|
|||
other code in this stage. So it's a safe way to ask the user
|
||||
for more input before any other installer steps start.
|
||||
"""
|
||||
if "nvidia" in _gfx_driver_packages:
|
||||
choice = input("The proprietary Nvidia driver is not supported by Sway. It is likely that you will run into issues. Continue anyways? [y/N] ")
|
||||
if choice.lower() in ("n", ""):
|
||||
raise archinstall.lib.exceptions.HardwareIncompatibilityError("Sway does not support the proprietary nvidia drivers.")
|
||||
|
||||
__builtins__['_gfx_driver_packages'] = archinstall.select_driver()
|
||||
|
||||
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()
|
||||
# Install the Sway packages
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import archinstall
|
|||
|
||||
is_top_level_profile = False
|
||||
|
||||
__packages__ = ["xfce4", "xfce4-goodies", "lightdm", "lightdm-gtk-greeter"]
|
||||
|
||||
def _prep_function(*args, **kwargs):
|
||||
"""
|
||||
Magic function called by the importing installer
|
||||
|
|
@ -28,8 +30,7 @@ if __name__ == 'xfce4':
|
|||
# Install dependency profiles
|
||||
installation.install_profile('xorg')
|
||||
|
||||
# Install the application xfce4 from the template under /applications/
|
||||
xfce = archinstall.Application(installation, 'xfce4')
|
||||
xfce.install()
|
||||
# Install the XFCE4 packages
|
||||
installation.add_additional_packages(__packages__)
|
||||
|
||||
installation.enable_service('lightdm') # Light Display Manager
|
||||
|
|
|
|||
|
|
@ -1,91 +1,11 @@
|
|||
# A system with "xorg" installed
|
||||
|
||||
import archinstall, os
|
||||
import os
|
||||
import archinstall
|
||||
|
||||
is_top_level_profile = True
|
||||
|
||||
AVAILABLE_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 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.")
|
||||
__packages__ = ['dkms', 'xorg-server', 'xorg-xinit', 'nvidia-dkms', 'xorg-server', *archinstall.lib.hardware.__packages__]
|
||||
|
||||
def _prep_function(*args, **kwargs):
|
||||
"""
|
||||
|
|
@ -94,10 +14,8 @@ def _prep_function(*args, **kwargs):
|
|||
other code in this stage. So it's a safe way to ask the user
|
||||
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
|
||||
# earlier in for instance guided.py installer.
|
||||
|
|
@ -109,7 +27,14 @@ def _prep_function(*args, **kwargs):
|
|||
# or through conventional import xorg
|
||||
if __name__ == 'xorg':
|
||||
try:
|
||||
installation.add_additional_packages(f"xorg-server xorg-xinit {' '.join(_gfx_driver_packages)}")
|
||||
if "nvidia" in _gfx_driver_packages:
|
||||
if "linux-zen" in installation.base_packages or "linux-lts" in installation.base_packages:
|
||||
installation.add_additional_packages("dkms")#I've had kernel regen fail if it wasn't installed before nvidia-dkms
|
||||
installation.add_additional_packages("xorg-server xorg-xinit nvidia-dkms")
|
||||
else:
|
||||
installation.add_additional_packages(f"xorg-server xorg-xinit {' '.join(_gfx_driver_packages)}")
|
||||
else:
|
||||
installation.add_additional_packages(f"xorg-server xorg-xinit {' '.join(_gfx_driver_packages)}")
|
||||
except:
|
||||
installation.add_additional_packages(f"xorg-server xorg-xinit") # Prep didn't run, so there's no driver to install
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,30 @@
|
|||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
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/","profiles"]
|
||||
exclude = ["docs/*.html", "docs/_static","docs/*.png","docs/*.psd"]
|
||||
|
||||
[tool.flit.metadata.requires-extra]
|
||||
doc = ["sphinx"]
|
||||
Loading…
Reference in New Issue