updated to latest commits

This commit is contained in:
advaithm 2021-04-02 09:48:41 +05:30
commit f4e616cd9e
No known key found for this signature in database
GPG Key ID: E557E45E6DAFFC0C
18 changed files with 402 additions and 157 deletions

31
.github/workflows/python-publish.yml vendored Normal file
View File

@ -0,0 +1,31 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
name: Upload archinstall to PyPi
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*

View File

@ -40,7 +40,12 @@ Also, do not squash your commits after you have submitted a pull request, as thi
At present the current contributors are (alphabetically): At present the current contributors are (alphabetically):
* Anton Hvornum ([@Torxed](https://github.com/Torxed)) * Anton Hvornum ([@Torxed](https://github.com/Torxed))
* Borislav Kosharov ([@nikibobi](https://github.com/nikibobi))
* demostanis ([@demostanis](https://github.com/demostanis)) * demostanis ([@demostanis](https://github.com/demostanis))
* Giancarlo Razzolini (@[grazzolini](https://github.com/grazzolini))
* j-james ([@j-james](https://github.com/j-james))
* Jerker Bengtsson ([@jaybent](https://github.com/jaybent)) * Jerker Bengtsson ([@jaybent](https://github.com/jaybent))
* Ninchester ([@ninchester](https://github.com/ninchester))
* Philipp Schaffrath ([@phisch](https://github.com/phisch))
* Varun Madiath ([@vamega](https://github.com/vamega)) * Varun Madiath ([@vamega](https://github.com/vamega))
* nullrequest ([@advaithm](https://github.com/advaithm)) * nullrequest ([@advaithm](https://github.com/advaithm))

View File

@ -2,7 +2,7 @@
# Contributor: Anton Hvornum anton@hvornum.se # Contributor: Anton Hvornum anton@hvornum.se
pkgname="archinstall-bin" pkgname="archinstall-bin"
pkgver="2.1.1" pkgver="2.1.3"
pkgdesc="Installs a pre-built binary of ${pkgname}" pkgdesc="Installs a pre-built binary of ${pkgname}"
pkgrel=1 pkgrel=1
url="https://github.com/Torxed/archinstall" url="https://github.com/Torxed/archinstall"

View File

@ -2,7 +2,7 @@
# Contributor: demostanis worlds <demostanis@protonmail.com> # Contributor: demostanis worlds <demostanis@protonmail.com>
pkgname="archinstall" pkgname="archinstall"
pkgver="2.1.1" pkgver="2.1.3"
pkgdesc="Installs launcher scripts for archinstall" pkgdesc="Installs launcher scripts for archinstall"
pkgrel=1 pkgrel=1
url="https://github.com/Torxed/archinstall" url="https://github.com/Torxed/archinstall"

View File

@ -2,7 +2,7 @@
# Contributor: demostanis worlds <demostanis@protonmail.com> # Contributor: demostanis worlds <demostanis@protonmail.com>
pkgname="python-archinstall" pkgname="python-archinstall"
pkgver="2.1.1" pkgver="2.1.3"
pkgdesc="Installs ${pkgname} as a python library." pkgdesc="Installs ${pkgname} as a python library."
pkgrel=1 pkgrel=1
url="https://github.com/Torxed/archinstall" url="https://github.com/Torxed/archinstall"

View File

@ -6,7 +6,6 @@ The installer also doubles as a python library to install Arch Linux and manage
* archinstall [matrix.org](https://app.element.io/#/room/#archinstall:matrix.org) channel * archinstall [matrix.org](https://app.element.io/#/room/#archinstall:matrix.org) channel
* archinstall [#archinstall@freenode (IRC)](irc://#archinstall@FreeNode) * archinstall [#archinstall@freenode (IRC)](irc://#archinstall@FreeNode)
* archinstall [documentation](https://python-archinstall.readthedocs.io/en/latest/index.html) * archinstall [documentation](https://python-archinstall.readthedocs.io/en/latest/index.html)
* archinstall ISO's: https://archlinux.life/
# Installation & Usage # Installation & Usage
@ -14,7 +13,7 @@ The installer also doubles as a python library to install Arch Linux and manage
$ sudo pacman -S archinstall $ sudo pacman -S archinstall
Or simply `git clone` the repo as it has no external dependencies *(but there are optional ones)*.<br> Or simply `git clone` the repo as it has no external dependencies *(but there are optional ones)*.<br>
Or run the pre-compiled binary attached in every release as `archinstall-v[ver].tar.gz`. Or use `pip install --upgrade archinstall` to use as a library.
## Running the [guided](examples/guided.py) installer ## Running the [guided](examples/guided.py) installer
@ -88,10 +87,5 @@ This will create a *5GB* `testimage.img` and create a loop device which we can u
`archinstall` is installed and executed in [guided mode](#docs-todo). Once the installation is complete,<br> `archinstall` is installed and executed in [guided mode](#docs-todo). Once the installation is complete,<br>
~~you can use qemu/kvm to boot the test media.~~ *(You'd actually need to do some EFI magic in order to point the EFI vars to the partition 0 in the test medium so this won't work entirely out of the box, but gives you a general idea of what we're going for here)* ~~you can use qemu/kvm to boot the test media.~~ *(You'd actually need to do some EFI magic in order to point the EFI vars to the partition 0 in the test medium so this won't work entirely out of the box, but gives you a general idea of what we're going for here)*
You can also run a pre-built ISO with pip and python There's also a [Building and Testing](https://github.com/Torxed/archinstall/wiki/Building-and-Testing) guide.<br>
It will go through everything from packaging, building and running *(with qemu)* the installer against a dev branch.
# qemu-system-x86_64 -enable-kvm -cdrom /home/user/Downloads/archinstall-2020.07.08-x86_64.iso -machine q35,accel=kvm -device intel-iommu -cpu host -m 4096 -boot order=d -drive file=./testimage.img,format=raw -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_CODE.fd -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_VARS.fd
and once inside, just do
# python -m archlinux guided

View File

@ -1 +1 @@
2.1.1 2.1.3

View File

@ -1 +1,3 @@
# This __init__ file is just here to support the
# use of archinstall as a git submodule.
from .archinstall import * from .archinstall import *

View File

@ -1,5 +1,5 @@
import glob, re, os, json, time, hashlib import glob, re, os, json, time, hashlib
import pathlib import pathlib, traceback
from collections import OrderedDict from collections import OrderedDict
from .exceptions import DiskError from .exceptions import DiskError
from .general import * from .general import *
@ -108,7 +108,7 @@ class BlockDevice():
if part_id not in self.part_cache: if part_id not in self.part_cache:
## TODO: Force over-write even if in cache? ## TODO: Force over-write even if in cache?
if part_id not in self.part_cache or self.part_cache[part_id].size != part['size']: if part_id not in self.part_cache or self.part_cache[part_id].size != part['size']:
self.part_cache[part_id] = Partition(root_path + part_id, part_id=part_id, size=part['size']) self.part_cache[part_id] = Partition(root_path + part_id, self, part_id=part_id, size=part['size'])
return {k: self.part_cache[k] for k in sorted(self.part_cache)} return {k: self.part_cache[k] for k in sorted(self.part_cache)}
@ -130,16 +130,22 @@ class BlockDevice():
return True return True
return False return False
def flush_cache(self):
self.part_cache = OrderedDict()
class Partition(): class Partition():
def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None, encrypted=False, autodetect_filesystem=True): def __init__(self, path :str, block_device :BlockDevice, part_id=None, size=-1, filesystem=None, mountpoint=None, encrypted=False, autodetect_filesystem=True):
if not part_id: if not part_id:
part_id = os.path.basename(path) part_id = os.path.basename(path)
self.block_device = block_device
self.path = path self.path = path
self.part_id = part_id self.part_id = part_id
self.mountpoint = mountpoint self.mountpoint = mountpoint
self.target_mountpoint = mountpoint self.target_mountpoint = mountpoint
self.filesystem = filesystem self.filesystem = filesystem
self.size = size # TODO: Refresh? self.size = size # TODO: Refresh?
self._encrypted = None
self.encrypted = encrypted self.encrypted = encrypted
self.allow_formatting = False # A fail-safe for unconfigured partitions, such as windows NTFS partitions. self.allow_formatting = False # A fail-safe for unconfigured partitions, such as windows NTFS partitions.
@ -175,28 +181,48 @@ class Partition():
elif self.target_mountpoint: elif self.target_mountpoint:
mount_repr = f", rel_mountpoint={self.target_mountpoint}" mount_repr = f", rel_mountpoint={self.target_mountpoint}"
if self.encrypted: if self._encrypted:
return f'Partition(path={self.path}, real_device={self.real_device}, fs={self.filesystem}{mount_repr})' return f'Partition(path={self.path}, real_device={self.real_device}, fs={self.filesystem}{mount_repr})'
else: else:
return f'Partition(path={self.path}, fs={self.filesystem}{mount_repr})' return f'Partition(path={self.path}, fs={self.filesystem}{mount_repr})'
@property
def encrypted(self):
return self._encrypted
@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
@property @property
def real_device(self): def real_device(self):
if not self.encrypted: if not self._encrypted:
return self.path return self.path
else: else:
for blockdevice in json.loads(b''.join(sys_command('lsblk -J')).decode('UTF-8'))['blockdevices']: for blockdevice in json.loads(b''.join(sys_command('lsblk -J')).decode('UTF-8'))['blockdevices']:
if (parent := self.find_parent_of(blockdevice, os.path.basename(self.path))): if (parent := self.find_parent_of(blockdevice, os.path.basename(self.path))):
return f"/dev/{parent}" return f"/dev/{parent}"
raise DiskError(f'Could not find appropriate parent for encrypted partition {self}') # raise DiskError(f'Could not find appropriate parent for encrypted partition {self}')
return self.path
def detect_inner_filesystem(self, password): 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=LOG_LEVELS.Info)
from .luks import luks2 from .luks import luks2
with luks2(self, 'luksloop', password, auto_unmount=True) as unlocked_device:
return unlocked_device.filesystem try:
with luks2(self, 'luksloop', password, auto_unmount=True) as unlocked_device:
return unlocked_device.filesystem
except SysCallError:
return None
def has_content(self): def has_content(self):
if not get_filesystem_type(self.path):
return False
temporary_mountpoint = '/tmp/'+hashlib.md5(bytes(f"{time.time()}", 'UTF-8')+os.urandom(12)).hexdigest() temporary_mountpoint = '/tmp/'+hashlib.md5(bytes(f"{time.time()}", 'UTF-8')+os.urandom(12)).hexdigest()
temporary_path = pathlib.Path(temporary_mountpoint) temporary_path = pathlib.Path(temporary_mountpoint)
@ -213,8 +239,10 @@ class Partition():
def safe_to_format(self): def safe_to_format(self):
if self.allow_formatting is False: if self.allow_formatting is False:
log(f"Partition {self} is not marked for formatting.", level=LOG_LEVELS.Debug)
return False return False
elif self.target_mountpoint == '/boot' and self.has_content(): elif self.target_mountpoint == '/boot' and self.has_content():
log(f"Partition {self} is a boot partition and has content inside.", level=LOG_LEVELS.Debug)
return False return False
return True return True
@ -225,10 +253,11 @@ class Partition():
""" """
from .luks import luks2 from .luks import luks2
if not self.encrypted: if not self._encrypted:
raise DiskError(f"Attempting to encrypt a partition that was not marked for encryption: {self}") raise DiskError(f"Attempting to encrypt a partition that was not marked for encryption: {self}")
if not self.safe_to_format(): 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")
return False return False
handle = luks2(self, None, None) handle = luks2(self, None, None)
@ -247,6 +276,11 @@ class Partition():
if allow_formatting is None: if allow_formatting is None:
allow_formatting = self.allow_formatting allow_formatting = self.allow_formatting
# To avoid "unable to open /dev/x: No such file or directory"
start_wait = time.time()
while pathlib.Path(path).exists() is False and time.time() - start_wait < 10:
time.sleep(0.025)
if not allow_formatting: if not allow_formatting:
raise PermissionError(f"{self} is not formatable either because instance is locked ({self.allow_formatting}) or a blocking flag was given ({allow_formatting})") raise PermissionError(f"{self} is not formatable either because instance is locked ({self.allow_formatting}) or a blocking flag was given ({allow_formatting})")
@ -288,6 +322,12 @@ class Partition():
else: else:
raise UnknownFilesystemFormat(f"Fileformat '{filesystem}' is not yet implemented.") raise UnknownFilesystemFormat(f"Fileformat '{filesystem}' is not yet implemented.")
if get_filesystem_type(path) == 'crypto_LUKS' or get_filesystem_type(self.real_device) == 'crypto_LUKS':
self.encrypted = True
else:
self.encrypted = False
return True return True
def find_parent_of(self, data, name, parent=None): def find_parent_of(self, data, name, parent=None):
@ -313,6 +353,24 @@ class Partition():
self.mountpoint = target self.mountpoint = target
return True return True
def unmount(self):
try:
exit_code = sys_command(f'/usr/bin/umount {self.path}').exit_code
except SysCallError as err:
exit_code = err.exit_code
# Without to much research, it seams that low error codes are errors.
# And above 8k is indicators such as "/dev/x not mounted.".
# So anything in between 0 and 8k are errors (?).
if exit_code > 0 and exit_code < 8000:
raise err
self.mountpoint = None
return True
def umount(self):
return self.unmount()
def filesystem_supported(self): def filesystem_supported(self):
""" """
The support for a filesystem (this partition) is tested by calling The support for a filesystem (this partition) is tested by calling
@ -343,7 +401,8 @@ class Filesystem():
if self.blockdevice.keep_partitions is False: 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=LOG_LEVELS.Debug)
if self.mode == GPT: if self.mode == GPT:
if sys_command(f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt',).exit_code == 0: if self.raw_parted(f'{self.blockdevice.device} mklabel gpt').exit_code == 0:
self.blockdevice.flush_cache()
return self return self
else: else:
raise DiskError(f'Problem setting the partition format to GPT:', f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt') raise DiskError(f'Problem setting the partition format to GPT:', f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt')
@ -380,7 +439,7 @@ class Filesystem():
def raw_parted(self, string:str): def raw_parted(self, string:str):
x = sys_command(f'/usr/bin/parted -s {string}') x = sys_command(f'/usr/bin/parted -s {string}')
o = b''.join(x) log(f"'parted -s {string}' returned: {b''.join(x)}", level=LOG_LEVELS.Debug)
return x return x
def parted(self, string:str): def parted(self, string:str):
@ -392,25 +451,33 @@ class Filesystem():
""" """
return self.raw_parted(string).exit_code return self.raw_parted(string).exit_code
def use_entire_disk(self, root_filesystem_type='ext4', encrypt_root_partition=True): def use_entire_disk(self, root_filesystem_type='ext4'):
self.add_partition('primary', start='1MiB', end='513MiB', format='vfat') log(f"Using and formatting the entire {self.blockdevice}.", level=LOG_LEVELS.Debug)
#TODO: figure out what do for bios, we don't need a seprate partion for the bootloader
if hasUEFI(): if hasUEFI():
self.add_partition('primary', start='1MiB', end='513MiB', format='fat32')
self.set_name(0, 'EFI') self.set_name(0, 'EFI')
self.set(0, 'boot on') self.set(0, 'boot on')
# TODO: Probably redundant because in GPT mode 'esp on' is an alias for "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 # https://www.gnu.org/software/parted/manual/html_node/set.html
self.set(0, 'esp on') self.set(0, 'esp on')
self.add_partition('primary', start='513MiB', end='100%') self.add_partition('primary', start='513MiB', end='100%')
self.blockdevice.partition[0].filesystem = 'vfat' self.blockdevice.partition[0].filesystem = 'vfat'
self.blockdevice.partition[1].filesystem = root_filesystem_type 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].target_mountpoint = '/boot' self.blockdevice.partition[0].target_mountpoint = '/boot'
self.blockdevice.partition[1].target_mountpoint = '/' self.blockdevice.partition[1].target_mountpoint = '/'
if encrypt_root_partition: self.blockdevice.partition[0].allow_formatting = True
self.blockdevice.partition[1].encrypted = 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=LOG_LEVELS.Debug)
self.blockdevice.partition[0].target_mountpoint = '/'
self.blockdevice.partition[0].allow_formatting = True
def add_partition(self, type, start, end, format=None): 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=LOG_LEVELS.Info)

View File

@ -7,7 +7,10 @@ class UnknownFilesystemFormat(BaseException):
class ProfileError(BaseException): class ProfileError(BaseException):
pass pass
class SysCallError(BaseException): class SysCallError(BaseException):
pass def __init__(self, message, exit_code):
super(SysCallError, self).__init__(message)
self.message = message
self.exit_code = exit_code
class ProfileNotFound(BaseException): class ProfileNotFound(BaseException):
pass pass
class HardwareIncompatibilityError(BaseException): class HardwareIncompatibilityError(BaseException):

View File

@ -105,8 +105,13 @@ class sys_command():#Thread):
self.status = 'starting' self.status = 'starting'
user_catalogue = os.path.expanduser('~') user_catalogue = os.path.expanduser('~')
self.cwd = f"{user_catalogue}/.cache/archinstall/workers/{kwargs['worker_id']}/"
self.exec_dir = f'{self.cwd}/{os.path.basename(self.cmd[0])}_workingdir' if (workdir := kwargs.get('workdir', None)):
self.cwd = workdir
self.exec_dir = workdir
else:
self.cwd = f"{user_catalogue}/.cache/archinstall/workers/{kwargs['worker_id']}/"
self.exec_dir = f'{self.cwd}/{os.path.basename(self.cmd[0])}_workingdir'
if not self.cmd[0][0] == '/': if not self.cmd[0][0] == '/':
# "which" doesn't work as it's a builtin to bash. # "which" doesn't work as it's a builtin to bash.
@ -251,7 +256,7 @@ class sys_command():#Thread):
if self.exit_code != 0 and not self.kwargs['suppress_errors']: 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(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(f"'{self.raw_cmd}' did not exit gracefully, exit code {self.exit_code}.", level=LOG_LEVELS.Error)
raise SysCallError(f"{self.trace_log.decode('UTF-8')}\n'{self.raw_cmd}' did not exit gracefully (trace log above), exit code: {self.exit_code}") 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() self.ended = time.time()
with open(f'{self.cwd}/trace.log', 'wb') as fh: with open(f'{self.cwd}/trace.log', 'wb') as fh:

View File

@ -1,4 +1,4 @@
import os, stat, time, shutil, subprocess import os, stat, time, shutil, pathlib
from .exceptions import * from .exceptions import *
from .disk import * from .disk import *
@ -9,7 +9,6 @@ from .mirrors import *
from .systemd import Networkd from .systemd import Networkd
from .output import log, LOG_LEVELS from .output import log, LOG_LEVELS
from .storage import storage from .storage import storage
from .hardware import *
class Installer(): class Installer():
""" """
@ -54,8 +53,6 @@ class Installer():
} }
self.base_packages = base_packages.split(' ') self.base_packages = base_packages.split(' ')
if not hasUEFI():
base_packages.append('grub') # if it isn't uefi is must be bios therefore we need grub as systemd-boot is uefi only
self.post_base_install = [] self.post_base_install = []
storage['session'] = self storage['session'] = self
@ -172,10 +169,19 @@ class Installer():
return True if sys_command(f'/usr/bin/arch-chroot {self.mountpoint} locale-gen').exit_code == 0 else False return True if sys_command(f'/usr/bin/arch-chroot {self.mountpoint} locale-gen').exit_code == 0 else False
def set_timezone(self, zone, *args, **kwargs): def set_timezone(self, zone, *args, **kwargs):
if not len(zone): return True if not zone: return True
if not len(zone): return True # Redundant
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} ln -s /usr/share/zoneinfo/{zone} /etc/localtime')) if (pathlib.Path("/usr")/"share"/"zoneinfo"/zone).exists():
return True (pathlib.Path(self.mountpoint)/"etc"/"localtime").unlink(missing_ok=True)
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} ln -s /usr/share/zoneinfo/{zone} /etc/localtime')
return True
else:
self.log(
f"Time zone {zone} does not exist, continuing with system default.",
level=LOG_LEVELS.Warning,
fg='red'
)
def activate_ntp(self): def activate_ntp(self):
self.log(f'Installing and activating NTP.', level=LOG_LEVELS.Info) self.log(f'Installing and activating NTP.', level=LOG_LEVELS.Info)
@ -292,13 +298,22 @@ class Installer():
# TODO: Use python functions for this # TODO: Use python functions for this
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} chmod 700 /root') sys_command(f'/usr/bin/arch-chroot {self.mountpoint} chmod 700 /root')
# Configure mkinitcpio to handle some specific use cases.
# TODO: Yes, we should not overwrite the entire thing, but for now this should be fine
# since we just installed the base system.
if self.partition.filesystem == 'btrfs': if self.partition.filesystem == 'btrfs':
with open(f'{self.mountpoint}/etc/mkinitcpio.conf', 'w') as mkinit: with open(f'{self.mountpoint}/etc/mkinitcpio.conf', 'w') as mkinit:
## TODO: Don't replace it, in case some update in the future actually adds something.
mkinit.write('MODULES=(btrfs)\n') mkinit.write('MODULES=(btrfs)\n')
mkinit.write('BINARIES=(/usr/bin/btrfs)\n') mkinit.write('BINARIES=(/usr/bin/btrfs)\n')
mkinit.write('FILES=()\n') mkinit.write('FILES=()\n')
mkinit.write('HOOKS=(base udev autodetect modconf block encrypt filesystems keyboard fsck)\n') mkinit.write('HOOKS=(base udev autodetect modconf block encrypt filesystems keymap keyboard fsck)\n')
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} mkinitcpio -p linux')
elif self.partition.encrypted:
with open(f'{self.mountpoint}/etc/mkinitcpio.conf', 'w') as mkinit:
mkinit.write('MODULES=()\n')
mkinit.write('BINARIES=()\n')
mkinit.write('FILES=()\n')
mkinit.write('HOOKS=(base udev autodetect modconf block encrypt filesystems keymap keyboard fsck)\n')
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} mkinitcpio -p linux') sys_command(f'/usr/bin/arch-chroot {self.mountpoint} mkinitcpio -p linux')
self.helper_flags['base'] = True self.helper_flags['base'] = True
@ -314,62 +329,75 @@ class Installer():
self.log(f'Adding bootloader {bootloader} to {self.boot_partition}', level=LOG_LEVELS.Info) self.log(f'Adding bootloader {bootloader} to {self.boot_partition}', level=LOG_LEVELS.Info)
if bootloader == 'systemd-bootctl': if bootloader == 'systemd-bootctl':
if hasUEFI(): # TODO: Ideally we would want to check if another config
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} bootctl --no-variables --path=/boot install')) # points towards the same disk and/or partition.
with open(f'{self.mountpoint}/boot/loader/loader.conf', 'w') as loader: # And in which case we should do some clean up.
loader.write('default arch\n')
loader.write('timeout 5\n')
## For some reason, blkid and /dev/disk/by-uuid are not getting along well. # Install the boot loader
## And blkid is wrong in terms of LUKS. sys_command(f'/usr/bin/arch-chroot {self.mountpoint} bootctl --no-variables --path=/boot install')
#UUID = sys_command('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip()
with open(f'{self.mountpoint}/boot/loader/entries/arch.conf', 'w') as entry:
entry.write('title Arch Linux\n')
entry.write('linux /vmlinuz-linux\n')
entry.write('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.
# Modify or create a loader.conf
if os.path.isfile(f'{self.mountpoint}/boot/loader/loader.conf'):
with open(f'{self.mountpoint}/boot/loader/loader.conf', 'r') as loader:
loader_data = loader.read().split('\n')
else:
loader_data = [
f"default {self.init_time}",
f"timeout 5"
]
if self.partition.encrypted: with open(f'{self.mountpoint}/boot/loader/loader.conf', 'w') as loader:
for root, folders, uids in os.walk('/dev/disk/by-uuid'): for line in loader_data:
for uid in uids: if line[:8] == 'default ':
real_path = os.path.realpath(os.path.join(root, uid)) loader.write(f'default {self.init_time}\n')
if not os.path.basename(real_path) == os.path.basename(self.partition.real_device): continue
if hasAMDCPU(): # intel_paste is intel only, it's redudant on AMD systens
entry.write(f'options cryptdevice=UUID={uid}:luksdev root=/dev/mapper/luksdev rw\n')
else:
entry.write(f'options cryptdevice=UUID={uid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n')
self.helper_flags['bootloader'] = bootloader
return True
break
else: else:
for root, folders, uids in os.walk('/dev/disk/by-partuuid'): loader.write(f"{line}")
for uid in uids:
real_path = os.path.realpath(os.path.join(root, uid))
if not os.path.basename(real_path) == os.path.basename(self.partition.path): continue
if hasAMDCPU():
entry.write(f'options root=PARTUUID={uid} rw\n')
else:
entry.write(f'options root=PARTUUID={uid} rw intel_pstate=no_hwp\n')
self.helper_flags['bootloader'] = bootloader ## For some reason, blkid and /dev/disk/by-uuid are not getting along well.
return True ## And blkid is wrong in terms of LUKS.
break #UUID = sys_command('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip()
raise RequirementError(f"Could not identify the UUID of {self.partition}, there for {self.mountpoint}/boot/loader/entries/arch.conf will be broken until fixed.")
else: # Setup the loader entry
raise RequirementError("Systemd-boot is UEFI only it can not be installed or used on bios") with open(f'{self.mountpoint}/boot/loader/entries/{self.init_time}.conf', 'w') as entry:
elif bootloader == 'grub-install': entry.write(f'# Created by: archinstall\n')
if hasUEFI(): entry.write(f'# Created on: {self.init_time}\n')
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB')) entry.write(f'title Arch Linux\n')
sys_command('/usr/bin/arch-chroot grub-mkconfig -o /boot/grub/grub.cfg') entry.write(f'linux /vmlinuz-linux\n')
else: entry.write(f'initrd /initramfs-linux.img\n')
root_device = subprocess.check_output(f'basename "$(readlink -f "/sys/class/block/{self.partition.path.strip("/dev/")}/..")',shell=True).decode().strip() ## blkid doesn't trigger on loopback devices really well,
if root_device == "block": ## so we'll use the old manual method until we get that sorted out.
root_device = f"{self.partition.path}"
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} grub-install --target=--target=i386-pc /dev/{root_device}'))
sys_command('/usr/bin/arch-chroot grub-mkconfig -o /boot/grub/grub.cfg') if self.partition.encrypted:
log(f"Identifying root partition by DISK-UUID on {self.partition}, looking for '{os.path.basename(self.partition.real_device)}'.", level=LOG_LEVELS.Debug)
for root, folders, uids in os.walk('/dev/disk/by-uuid'):
for uid in uids:
real_path = os.path.realpath(os.path.join(root, uid))
log(f"Checking root partition match {os.path.basename(real_path)} against {os.path.basename(self.partition.real_device)}: {os.path.basename(real_path) == os.path.basename(self.partition.real_device)}", level=LOG_LEVELS.Debug)
if not os.path.basename(real_path) == os.path.basename(self.partition.real_device): continue
entry.write(f'options cryptdevice=UUID={uid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n')
self.helper_flags['bootloader'] = bootloader
return True
break
else:
log(f"Identifying root partition by PART-UUID on {self.partition}, looking for '{os.path.basename(self.partition.path)}'.", level=LOG_LEVELS.Debug)
for root, folders, uids in os.walk('/dev/disk/by-partuuid'):
for uid in uids:
real_path = os.path.realpath(os.path.join(root, uid))
log(f"Checking root partition match {os.path.basename(real_path)} against {os.path.basename(self.partition.path)}: {os.path.basename(real_path) == os.path.basename(self.partition.path)}", level=LOG_LEVELS.Debug)
if not os.path.basename(real_path) == os.path.basename(self.partition.path): continue
entry.write(f'options root=PARTUUID={uid} rw intel_pstate=no_hwp\n')
self.helper_flags['bootloader'] = bootloader
return True
break
raise RequirementError(f"Could not identify the UUID of {self.partition}, there for {self.mountpoint}/boot/loader/entries/arch.conf will be broken until fixed.")
else: else:
raise RequirementError(f"Unknown (or not yet implemented) bootloader added to add_bootloader(): {bootloader}") raise RequirementError(f"Unknown (or not yet implemented) bootloader added to add_bootloader(): {bootloader}")
@ -377,7 +405,17 @@ class Installer():
return self.pacstrap(*packages) return self.pacstrap(*packages)
def install_profile(self, profile): def install_profile(self, profile):
profile = Profile(self, profile) # TODO: Replace this with a import archinstall.session instead in the profiles.
# The tricky thing with doing the import archinstall.session instead is that
# profiles might be run from a different chroot, and there's no way we can
# guarantee file-path safety when accessing the installer object that way.
# Doing the __builtins__ replacement, ensures that the global vriable "installation"
# is always kept up to date. It's considered a nasty hack - but it's a safe way
# of ensuring 100% accuracy of archinstall session variables.
__builtins__['installation'] = self
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=LOG_LEVELS.Info)
return profile.install() return profile.install()

View File

@ -64,8 +64,37 @@ class luks2():
with open(key_file, 'wb') as fh: with open(key_file, 'wb') as fh:
fh.write(password) fh.write(password)
o = b''.join(sys_command(f'/usr/bin/cryptsetup -q -v --type luks2 --pbkdf argon2i --hash {hash_type} --key-size {key_size} --iter-time {iter_time} --key-file {os.path.abspath(key_file)} --use-urandom luksFormat {partition.path}')) try:
if b'Command successful.' not in o: # Try to setup the crypt-device
cmd_handle = sys_command(f'/usr/bin/cryptsetup -q -v --type luks2 --pbkdf argon2i --hash {hash_type} --key-size {key_size} --iter-time {iter_time} --key-file {os.path.abspath(key_file)} --use-urandom luksFormat {partition.path}')
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)
# Partition was in use, unmount it and try again
partition.unmount()
# Get crypt-information about the device by doing a reverse lookup starting with the partition path
# For instance: /dev/sda
devinfo = json.loads(b''.join(sys_command(f"lsblk --fs -J {partition.path}")).decode('UTF-8'))['blockdevices'][0]
# For each child (sub-partition/sub-device)
if len(children := devinfo.get('children', [])):
for child in children:
# Unmount the child location
if child_mountpoint := child.get('mountpoint', None):
log(f'Unmounting {child_mountpoint}', level=LOG_LEVELS.Debug)
sys_command(f"umount -R {child_mountpoint}")
# And close it if possible.
log(f"Closing crypt device {child['name']}", level=LOG_LEVELS.Debug)
sys_command(f"cryptsetup close {child['name']}")
# Then try again to set up the crypt-device
cmd_handle = sys_command(f'/usr/bin/cryptsetup -q -v --type luks2 --pbkdf argon2i --hash {hash_type} --key-size {key_size} --iter-time {iter_time} --key-file {os.path.abspath(key_file)} --use-urandom luksFormat {partition.path}')
else:
raise err
if b'Command successful.' not in b''.join(cmd_handle):
raise DiskError(f'Could not encrypt volume "{partition.path}": {o}') raise DiskError(f'Could not encrypt volume "{partition.path}": {o}')
return key_file return key_file
@ -84,7 +113,7 @@ class luks2():
sys_command(f'/usr/bin/cryptsetup open {partition.path} {mountpoint} --key-file {os.path.abspath(key_file)} --type luks2') sys_command(f'/usr/bin/cryptsetup open {partition.path} {mountpoint} --key-file {os.path.abspath(key_file)} --type luks2')
if os.path.islink(f'/dev/mapper/{mountpoint}'): if os.path.islink(f'/dev/mapper/{mountpoint}'):
self.mapdev = f'/dev/mapper/{mountpoint}' self.mapdev = f'/dev/mapper/{mountpoint}'
unlocked_partition = Partition(self.mapdev, encrypted=True, filesystem=get_filesystem_type(self.mapdev), autodetect_filesystem=False) unlocked_partition = Partition(self.mapdev, None, encrypted=True, filesystem=get_filesystem_type(self.mapdev), autodetect_filesystem=False)
unlocked_partition.allow_formatting = self.partition.allow_formatting unlocked_partition.allow_formatting = self.partition.allow_formatting
return unlocked_partition return unlocked_partition

View File

@ -96,7 +96,17 @@ def log(*args, **kwargs):
if (filename := storage.get('LOG_FILE', None)): if (filename := storage.get('LOG_FILE', None)):
absolute_logfile = os.path.join(storage.get('LOG_PATH', './'), filename) absolute_logfile = os.path.join(storage.get('LOG_PATH', './'), filename)
if not os.path.isfile(absolute_logfile): if not os.path.isfile(absolute_logfile):
os.makedirs(os.path.dirname(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? Path(absolute_logfile).touch() # Overkill?
with open(absolute_logfile, 'a') as log_file: with open(absolute_logfile, 'a') as log_file:

View File

@ -1,7 +1,7 @@
import os, urllib.request, urllib.parse, ssl, json, re import os, urllib.request, urllib.parse, ssl, json, re
import importlib.util, sys, glob, hashlib import importlib.util, sys, glob, hashlib
from collections import OrderedDict from collections import OrderedDict
from .general import multisplit, sys_command, log from .general import multisplit, sys_command
from .exceptions import * from .exceptions import *
from .networking import * from .networking import *
from .output import log, LOG_LEVELS from .output import log, LOG_LEVELS
@ -76,6 +76,8 @@ class Script():
self.spec = None self.spec = None
self.examples = None self.examples = None
self.namespace = os.path.splitext(os.path.basename(self.path))[0] 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): def __enter__(self, *args, **kwargs):
self.execute() self.execute()
@ -138,7 +140,6 @@ class Script():
if not self.namespace in sys.modules or self.spec is None: if not self.namespace in sys.modules or self.spec is None:
self.load_instructions() self.load_instructions()
__builtins__['installation'] = self.installer # TODO: Replace this with a import archinstall.session instead
self.spec.loader.exec_module(sys.modules[self.namespace]) self.spec.loader.exec_module(sys.modules[self.namespace])
return sys.modules[self.namespace] return sys.modules[self.namespace]
@ -146,7 +147,6 @@ class Script():
class Profile(Script): class Profile(Script):
def __init__(self, installer, path, args={}): def __init__(self, installer, path, args={}):
super(Profile, self).__init__(path, installer) super(Profile, self).__init__(path, installer)
self._cache = None
def __dump__(self, *args, **kwargs): def __dump__(self, *args, **kwargs):
return {'path' : self.path} return {'path' : self.path}
@ -155,6 +155,10 @@ class Profile(Script):
return f'Profile({os.path.basename(self.profile)})' return f'Profile({os.path.basename(self.profile)})'
def install(self): def install(self):
# Before installing, revert any temporary changes to the namespace.
# This ensures that the namespace during installation is the original initation namespace.
# (For instance awesome instead of aweosme.py or app-awesome.py)
self.namespace = self.original_namespace
return self.execute() return self.execute()
def has_prep_function(self): def has_prep_function(self):
@ -203,3 +207,10 @@ class Application(Profile):
return self.localize_path(self.profile) return self.localize_path(self.profile)
else: else:
raise ProfileNotFound(f"Application cannot handle scheme {parsed_url.scheme}") raise ProfileNotFound(f"Application cannot handle scheme {parsed_url.scheme}")
def install(self):
# Before installing, revert any temporary changes to the namespace.
# This ensures that the namespace during installation is the original initation namespace.
# (For instance awesome instead of aweosme.py or app-awesome.py)
self.namespace = self.original_namespace
return self.execute()

View File

@ -1,4 +1,4 @@
import getpass import getpass, pathlib, os, shutil
from .exceptions import * from .exceptions import *
from .profiles import Profile from .profiles import Profile
from .locale_helpers import search_keyboard_layout from .locale_helpers import search_keyboard_layout
@ -9,15 +9,44 @@ from .networking import list_interfaces
## TODO: Some inconsistencies between the selection processes. ## TODO: Some inconsistencies between the selection processes.
## Some return the keys from the options, some the values? ## Some return the keys from the options, some the values?
def get_terminal_height():
return shutil.get_terminal_size().lines
def get_terminal_width():
return shutil.get_terminal_size().columns
def get_longest_option(options):
return max([len(x) for x in options])
def get_password(prompt="Enter a password: "): def get_password(prompt="Enter a password: "):
while (passwd := getpass.getpass(prompt)): while (passwd := getpass.getpass(prompt)):
passwd_verification = getpass.getpass(prompt='And one more time for verification: ') passwd_verification = getpass.getpass(prompt='And one more time for verification: ')
if passwd != passwd_verification: if passwd != passwd_verification:
log(' * Passwords did not match * ', bg='black', fg='red') log(' * Passwords did not match * ', bg='black', fg='red')
continue continue
if len(passwd.strip()) <= 0:
break
return passwd return passwd
return None return None
def print_large_list(options, padding=5, margin_bottom=0, separator=': '):
highest_index_number_length = len(str(len(options)))
longest_line = highest_index_number_length + len(separator) + get_longest_option(options) + padding
max_num_of_columns = get_terminal_width() // longest_line
max_options_in_cells = max_num_of_columns * (get_terminal_height()-margin_bottom)
if (len(options) > max_options_in_cells):
for index, option in enumerate(options):
print(f"{index}: {option}")
else:
for row in range(0, (get_terminal_height()-margin_bottom)):
for column in range(row, len(options), (get_terminal_height()-margin_bottom)):
spaces = " "*(longest_line - len(options[column]))
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): def ask_for_superuser_account(prompt='Create a required super-user with sudo privileges: ', forced=False):
while 1: while 1:
new_user = input(prompt).strip(' ') new_user = input(prompt).strip(' ')
@ -31,7 +60,7 @@ def ask_for_superuser_account(prompt='Create a required super-user with sudo pri
raise UserError("No superuser was created.") raise UserError("No superuser was created.")
password = get_password(prompt=f'Password for user {new_user}: ') password = get_password(prompt=f'Password for user {new_user}: ')
return {new_user: password} return {new_user: {"!password" : password}}
def ask_for_additional_users(prompt='Any additional users to install (leave blank for no users): '): def ask_for_additional_users(prompt='Any additional users to install (leave blank for no users): '):
users = {} users = {}
@ -44,12 +73,23 @@ def ask_for_additional_users(prompt='Any additional users to install (leave blan
password = get_password(prompt=f'Password for user {new_user}: ') password = get_password(prompt=f'Password for user {new_user}: ')
if input("Should this user be a sudo (super) user (y/N): ").strip(' ').lower() in ('y', 'yes'): if input("Should this user be a sudo (super) user (y/N): ").strip(' ').lower() in ('y', 'yes'):
super_users[new_user] = password super_users[new_user] = {"!password" : password}
else: else:
users[new_user] = password users[new_user] = {"!password" : password}
return users, super_users return users, super_users
def ask_for_a_timezone():
timezone = input('Enter a valid timezone (Example: Europe/Stockholm): ').strip()
if (pathlib.Path("/usr")/"share"/"zoneinfo"/timezone).exists():
return timezone
else:
log(
f"Time zone {timezone} does not exist, continuing with system default.",
level=LOG_LEVELS.Warning,
fg='red'
)
def ask_to_configure_network(): def ask_to_configure_network():
# Optionally configure one network interface. # Optionally configure one network interface.
#while 1: #while 1:
@ -102,11 +142,10 @@ def ask_for_main_filesystem_format():
'btrfs' : 'btrfs', 'btrfs' : 'btrfs',
'ext4' : 'ext4', 'ext4' : 'ext4',
'xfs' : 'xfs', 'xfs' : 'xfs',
'f2fs' : 'f2fs', 'f2fs' : 'f2fs'
'vfat' : 'vfat'
} }
value = generic_select(options.values(), "Select your main partitions filesystem by number or free-text: ") value = generic_select(options.values(), "Select which filesystem your main partition should use (by number or name): ")
return next((key for key, val in options.items() if val == value), None) 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: ", sort=True):
@ -131,7 +170,10 @@ def generic_select(options, input_text="Select one of the above by index or abso
if len(selected_option.strip()) <= 0: if len(selected_option.strip()) <= 0:
return None return None
elif selected_option.isdigit(): elif selected_option.isdigit():
selected_option = options[int(selected_option)] 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: elif selected_option in options:
pass # We gave a correct absolute value pass # We gave a correct absolute value
else: else:
@ -156,7 +198,10 @@ def select_disk(dict_o_disks):
print(f"{index}: {drive} ({dict_o_disks[drive]['size'], dict_o_disks[drive].device, dict_o_disks[drive]['label']})") print(f"{index}: {drive} ({dict_o_disks[drive]['size'], dict_o_disks[drive].device, dict_o_disks[drive]['label']})")
drive = input('Select one of the above disks (by number or full path): ') drive = input('Select one of the above disks (by number or full path): ')
if drive.isdigit(): if drive.isdigit():
drive = dict_o_disks[drives[int(drive)]] 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: elif drive in dict_o_disks:
drive = dict_o_disks[drive] drive = dict_o_disks[drive]
else: else:
@ -182,10 +227,10 @@ def select_profile(options):
for index, profile in enumerate(profiles): for index, profile in enumerate(profiles):
print(f"{index}: {profile}") print(f"{index}: {profile}")
print(' -- The above list is pre-programmed profiles. --') print(' -- The above list is a set of pre-programmed profiles. --')
print(' -- They might make it easier to install things like desktop environments. --') print(' -- They might make it easier to install things like desktop environments. --')
print(' -- (Leave blank to skip this next optional step) --') print(' -- (Leave blank and hit enter to skip this step and continue) --')
selected_profile = input('Any particular pre-programmed profile you want to install: ') selected_profile = input('Enter a pre-programmed profile name if you want to install one: ')
if len(selected_profile.strip()) <= 0: if len(selected_profile.strip()) <= 0:
return None return None
@ -265,24 +310,13 @@ def select_mirror_regions(mirrors, show_top_mirrors=True):
selected_mirrors = {} selected_mirrors = {}
if len(regions) >= 1: if len(regions) >= 1:
for index, region in enumerate(regions): print_large_list(regions, margin_bottom=2)
print(f"{index}: {region}")
print(' -- You can enter ? or help to search for more regions --')
print(' -- You can skip this step by leaving the option blank --') print(' -- You can skip this step by leaving the option blank --')
print(' -- (You can use Shift + PageUp to scroll in the list --')
selected_mirror = input('Select one of the above regions to download packages from (by number or full name): ') selected_mirror = input('Select one of the above regions to download packages from (by number or full name): ')
if len(selected_mirror.strip()) == 0: if len(selected_mirror.strip()) == 0:
return {} return {}
elif selected_mirror.lower() in ('?', 'help'):
filter_string = input('Search for a region containing (example: "united"): ').strip().lower()
for region in mirrors:
if filter_string in region.lower():
selected_mirrors[region] = mirrors[region]
return selected_mirrors
elif selected_mirror.isdigit() and (pos := int(selected_mirror)) <= len(regions)-1: elif selected_mirror.isdigit() and (pos := int(selected_mirror)) <= len(regions)-1:
region = regions[int(selected_mirror)] region = regions[int(selected_mirror)]
selected_mirrors[region] = mirrors[region] selected_mirrors[region] = mirrors[region]

View File

@ -53,7 +53,7 @@ def ask_user_questions():
# 3. Check that we support the current partitions # 3. Check that we support the current partitions
# 2. If so, ask if we should keep them or wipe everything # 2. If so, ask if we should keep them or wipe everything
if archinstall.arguments['harddrive'].has_partitions(): if archinstall.arguments['harddrive'].has_partitions():
archinstall.log(f"{archinstall.arguments['harddrive']} contains the following partitions:", fg='red') archinstall.log(f"{archinstall.arguments['harddrive']} contains the following partitions:", fg='yellow')
# We curate a list pf supported paritions # We curate a list pf supported paritions
# and print those that we don't support. # and print those that we don't support.
@ -91,7 +91,11 @@ def ask_user_questions():
new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ') new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ')
if len(new_filesystem) <= 0: if len(new_filesystem) <= 0:
if partition.encrypted and partition.filesystem == 'crypto_LUKS': if partition.encrypted and partition.filesystem == 'crypto_LUKS':
if (autodetected_filesystem := partition.detect_inner_filesystem(archinstall.arguments.get('!encryption-password', None))): old_password = archinstall.arguments.get('!encryption-password', None)
if not old_password:
old_password = input(f'Enter the old encryption password for {partition}: ')
if (autodetected_filesystem := partition.detect_inner_filesystem(old_password)):
new_filesystem = autodetected_filesystem new_filesystem = autodetected_filesystem
else: else:
archinstall.log(f"Could not auto-detect the filesystem inside the encrypted volume.", fg='red') archinstall.log(f"Could not auto-detect the filesystem inside the encrypted volume.", fg='red')
@ -127,11 +131,17 @@ def ask_user_questions():
elif option == 'format-all': elif option == 'format-all':
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format() archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
archinstall.arguments['harddrive'].keep_partitions = False archinstall.arguments['harddrive'].keep_partitions = False
else:
# 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()
archinstall.arguments['harddrive'].keep_partitions = False
# Get disk encryption password (or skip if blank) # Get disk encryption password (or skip if blank)
if not archinstall.arguments.get('!encryption-password', None): if not archinstall.arguments.get('!encryption-password', None):
archinstall.arguments['!encryption-password'] = archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ') if (passwd := archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ')):
archinstall.arguments['harddrive'].encryption_password = archinstall.arguments['!encryption-password'] archinstall.arguments['!encryption-password'] = passwd
archinstall.arguments['harddrive'].encryption_password = archinstall.arguments['!encryption-password']
# Get the hostname for the machine # Get the hostname for the machine
if not archinstall.arguments.get('hostname', None): if not archinstall.arguments.get('hostname', None):
@ -139,7 +149,7 @@ def ask_user_questions():
# Ask for a root password (optional, but triggers requirement for super-user if skipped) # Ask for a root password (optional, but triggers requirement for super-user if skipped)
if not archinstall.arguments.get('!root-password', None): if not archinstall.arguments.get('!root-password', None):
archinstall.arguments['!root-password'] = archinstall.get_password(prompt='Enter root password (Recommended: leave blank to leave root disabled): ') archinstall.arguments['!root-password'] = archinstall.get_password(prompt='Enter root password (Recommendation: leave blank to leave root disabled): ')
# Ask for additional users (super-user if root pw was not set) # Ask for additional users (super-user if root pw was not set)
archinstall.arguments['users'] = {} archinstall.arguments['users'] = {}
@ -147,7 +157,7 @@ def ask_user_questions():
if not archinstall.arguments.get('!root-password', None): if not archinstall.arguments.get('!root-password', None):
archinstall.arguments['superusers'] = archinstall.ask_for_superuser_account('Create a required super-user with sudo privileges: ', forced=True) archinstall.arguments['superusers'] = archinstall.ask_for_superuser_account('Create a required super-user with sudo privileges: ', forced=True)
users, superusers = archinstall.ask_for_additional_users('Any additional users to install (leave blank for no users): ') users, superusers = archinstall.ask_for_additional_users('Enter a username to create a additional user (leave blank to skip & continue): ')
archinstall.arguments['users'] = users archinstall.arguments['users'] = users
archinstall.arguments['superusers'] = {**archinstall.arguments['superusers'], **superusers} archinstall.arguments['superusers'] = {**archinstall.arguments['superusers'], **superusers}
@ -170,7 +180,7 @@ def ask_user_questions():
# Additional packages (with some light weight error handling for invalid package names) # Additional packages (with some light weight error handling for invalid package names)
if not archinstall.arguments.get('packages', None): if not archinstall.arguments.get('packages', None):
archinstall.arguments['packages'] = [package for package in input('Additional packages aside from base (space separated): ').split(' ') if len(package)] archinstall.arguments['packages'] = [package for package in input('Write additional packages to install (space separated, leave blank to skip): ').split(' ') if len(package)]
# Verify packages that were given # Verify packages that were given
try: try:
@ -182,6 +192,11 @@ def ask_user_questions():
# Ask or Call the helper function that asks the user to optionally configure a network. # Ask or Call the helper function that asks the user to optionally configure a network.
if not archinstall.arguments.get('nic', None): if not archinstall.arguments.get('nic', None):
archinstall.arguments['nic'] = archinstall.ask_to_configure_network() archinstall.arguments['nic'] = archinstall.ask_to_configure_network()
if not archinstall.arguments['nic']:
archinstall.log(f"No network configuration was selected. Network is going to be unavailable until configured manually!", fg="yellow")
if not archinstall.arguments.get('timezone', None):
archinstall.arguments['timezone'] = archinstall.ask_for_a_timezone()
def perform_installation_steps(): def perform_installation_steps():
@ -232,10 +247,10 @@ def perform_installation_steps():
with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs: with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs:
# Wipe the entire drive if the disk flag `keep_partitions`is False. # Wipe the entire drive if the disk flag `keep_partitions`is False.
if archinstall.arguments['harddrive'].keep_partitions is False: if archinstall.arguments['harddrive'].keep_partitions is False:
fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'), fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'))
encrypt_root_partition=archinstall.arguments.get('!encryption-password', False))
# Otherwise, check if encryption is desired and mark the root partition as encrypted. # Check if encryption is desired and mark the root partition as encrypted.
elif archinstall.arguments.get('!encryption-password', None): if archinstall.arguments.get('!encryption-password', None):
root_partition = fs.find_partition('/') root_partition = fs.find_partition('/')
root_partition.encrypted = True root_partition.encrypted = True
@ -243,8 +258,11 @@ def perform_installation_steps():
# which ones are safe to format, and format those. # which ones are safe to format, and format those.
for partition in archinstall.arguments['harddrive']: for partition in archinstall.arguments['harddrive']:
if partition.safe_to_format(): if partition.safe_to_format():
if partition.encrypted: # Partition might be marked as encrypted due to the filesystem type crypt_LUKS
partition.encrypt(password=archinstall.arguments.get('!encryption-password', None)) # But we might have omitted the encryption password question to skip encryption.
# In which case partition.encrypted will be true, but passwd will be false.
if partition.encrypted and (passwd := archinstall.arguments.get('!encryption-password', None)):
partition.encrypt(password=passwd)
else: else:
partition.format() partition.format()
else: else:
@ -262,7 +280,6 @@ def perform_installation_steps():
language=archinstall.arguments['keyboard-language'], language=archinstall.arguments['keyboard-language'],
mirrors=archinstall.arguments['mirror-region']) mirrors=archinstall.arguments['mirror-region'])
else: else:
archinstall.arguments['harddrive'].partition[1].format('ext4')
perform_installation(device=fs.find_partition('/'), perform_installation(device=fs.find_partition('/'),
boot_partition=fs.find_partition('/boot'), boot_partition=fs.find_partition('/boot'),
language=archinstall.arguments['keyboard-language'], language=archinstall.arguments['keyboard-language'],
@ -305,18 +322,17 @@ def perform_installation(device, boot_partition, language, mirrors):
if archinstall.arguments.get('packages', None) and archinstall.arguments.get('packages', None)[0] != '': if archinstall.arguments.get('packages', None) and archinstall.arguments.get('packages', None)[0] != '':
installation.add_additional_packages(archinstall.arguments.get('packages', None)) installation.add_additional_packages(archinstall.arguments.get('packages', None))
if archinstall.arguments.get('profile', None) and len(profile := archinstall.arguments.get('profile').strip()): if archinstall.arguments.get('profile', None):
installation.install_profile(profile) installation.install_profile(archinstall.arguments.get('profile', None))
if archinstall.arguments.get('users', None): for user, user_info in archinstall.arguments.get('users', {}).items():
for user in archinstall.arguments.get('users'): installation.user_create(user, user_info["!password"], sudo=False)
password = users[user]
installation.user_create(user, password, sudo=False)
if archinstall.arguments.get('superusers', None):
for user in archinstall.arguments.get('users'):
password = users[user]
installation.user_create(user, password, sudo=Tru)
for superuser, user_info in archinstall.arguments.get('superusers', {}).items():
installation.user_create(superuser, user_info["!password"], sudo=True)
if (timezone := archinstall.arguments.get('timezone', None)):
installation.set_timezone(timezone)
if (root_pw := archinstall.arguments.get('!root-password', None)) and len(root_pw): if (root_pw := archinstall.arguments.get('!root-password', None)) and len(root_pw):
installation.user_set_pw('root', root_pw) installation.user_set_pw('root', root_pw)

View File

@ -32,7 +32,7 @@ if __name__ == 'awesome':
editor = "nano" editor = "nano"
filebrowser = "nemo gpicview-gtk3" filebrowser = "nemo gpicview-gtk3"
webbrowser = "chromium" # TODO: Ask the user to select one instead webbrowser = "chromium" # TODO: Ask the user to select one instead
utils = "openssh sshfs git htop pkgfile scrot dhclient wget libu2f-host" utils = "openssh sshfs htop scrot wget"
installation.add_additional_packages(f"{webbrowser} {utils} {filebrowser} {editor}") installation.add_additional_packages(f"{webbrowser} {utils} {filebrowser} {editor}")