Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
path: root/archinstall
diff options
context:
space:
mode:
Diffstat (limited to 'archinstall')
-rw-r--r--archinstall/__init__.py2
-rw-r--r--archinstall/lib/disk.py36
-rw-r--r--archinstall/lib/general.py54
-rw-r--r--archinstall/lib/hardware.py18
-rw-r--r--archinstall/lib/installer.py24
-rw-r--r--archinstall/lib/luks.py25
-rw-r--r--archinstall/lib/profiles.py22
-rw-r--r--archinstall/lib/systemd.py16
-rw-r--r--archinstall/lib/user_interaction.py13
9 files changed, 151 insertions, 59 deletions
diff --git a/archinstall/__init__.py b/archinstall/__init__.py
index d4452d38..91cf17be 100644
--- a/archinstall/__init__.py
+++ b/archinstall/__init__.py
@@ -2,7 +2,7 @@ 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__, __base_packages__, Installer
from .lib.profiles import *
from .lib.luks import *
from .lib.mirrors import *
diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py
index fdc2fbc5..4ea9f214 100644
--- a/archinstall/lib/disk.py
+++ b/archinstall/lib/disk.py
@@ -73,26 +73,31 @@ 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']
elif self.info['type'] == 'disk':
return self.path
+ elif self.info['type'][:4] == 'raid':
+ # This should catch /dev/md## raid devices
+ return self.path
elif self.info['type'] == 'crypt':
if 'pkname' not in self.info:
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)
# 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}')
@@ -187,6 +192,17 @@ class Partition():
return f'Partition(path={self.path}, fs={self.filesystem}{mount_repr})'
@property
+ def uuid(self) -> str:
+ """
+ Returns the PARTUUID as returned by lsblk.
+ This is more reliable than relying on /dev/disk/by-partuuid as
+ it doesn't seam to be able to detect md raid partitions.
+ """
+ lsblk = b''.join(sys_command(f'lsblk -J -o+PARTUUID {self.path}'))
+ for partition in json.loads(lsblk.decode('UTF-8'))['blockdevices']:
+ return partition.get('partuuid', None)
+
+ @property
def encrypted(self):
return self._encrypted
@@ -203,7 +219,7 @@ class Partition():
if not self._encrypted:
return self.path
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))):
return f"/dev/{parent}"
# raise DiskError(f'Could not find appropriate parent for encrypted partition {self}')
@@ -241,9 +257,15 @@ class Partition():
if self.allow_formatting is False:
log(f"Partition {self} is not marked for formatting.", level=LOG_LEVELS.Debug)
return False
- 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
+ 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)
+ 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)
+ pass
return True
diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py
index f2a714e7..5b1b3c2a 100644
--- a/archinstall/lib/general.py
+++ b/archinstall/lib/general.py
@@ -76,7 +76,7 @@ class sys_command():#Thread):
"""
Stolen from archinstall_gui
"""
- def __init__(self, cmd, callback=None, start_callback=None, *args, **kwargs):
+ def __init__(self, cmd, callback=None, start_callback=None, peak_output=False, *args, **kwargs):
kwargs.setdefault("worker_id", gen_uid())
kwargs.setdefault("emulate", False)
kwargs.setdefault("suppress_errors", False)
@@ -86,13 +86,22 @@ class sys_command():#Thread):
if kwargs['emulate']:
self.log(f"Starting command '{cmd}' in emulation mode.", level=LOG_LEVELS.Debug)
- self.raw_cmd = cmd
- try:
- self.cmd = shlex.split(cmd)
- except Exception as e:
- raise ValueError(f'Incorrect string to split: {cmd}\n{e}')
+ 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.args = args
self.kwargs = kwargs
+ self.peak_output = peak_output
self.kwargs.setdefault("worker", None)
self.callback = callback
@@ -150,6 +159,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()
@@ -181,6 +222,7 @@ 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
diff --git a/archinstall/lib/hardware.py b/archinstall/lib/hardware.py
index 3586ad09..10f3970f 100644
--- a/archinstall/lib/hardware.py
+++ b/archinstall/lib/hardware.py
@@ -3,9 +3,7 @@ from .general import sys_command
from .networking import list_interfaces, enrichIfaceTypes
def hasWifi():
- if 'WIRELESS' in enrichIfaceTypes(list_interfaces().values()).values():
- return True
- return False
+ return 'WIRELESS' in enrichIfaceTypes(list_interfaces().values()).values()
def hasAMDCPU():
if subprocess.check_output("lscpu | grep AMD", shell=True).strip().decode():
@@ -24,18 +22,12 @@ def graphicsDevices():
return cards
def hasNvidiaGraphics():
- if [x for x in graphicsDevices() if 'nvidia' in x]:
- return True
- return False
+ return any('nvidia' in x for x in graphicsDevices())
def hasAmdGraphics():
- if [x for x in graphicsDevices() if 'amd' in x]:
- return True
- return False
+ return any('amd' in x for x in graphicsDevices())
def hasIntelGraphics():
- if [x for x in graphicsDevices() if 'intel' in x]:
- return True
- return False
+ return any('intel' in x for x in graphicsDevices())
-# TODO: Add more identifiers \ No newline at end of file
+# TODO: Add more identifiers
diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py
index e38860d3..a99bc944 100644
--- a/archinstall/lib/installer.py
+++ b/archinstall/lib/installer.py
@@ -10,6 +10,10 @@ from .systemd import Networkd
from .output import log, LOG_LEVELS
from .storage import storage
+# Any package that the Installer() is responsible for (optional and the default ones)
+__packages__ = ["base", "base-devel", "linux", "linux-firmware", "efibootmgr", "nano", "ntp", "iwd"]
+__base_packages__ = __packages__[:6]
+
class Installer():
"""
`Installer()` is the wrapper for most basic installation steps.
@@ -34,7 +38,7 @@ class Installer():
:type hostname: str, optional
"""
- def __init__(self, partition, boot_partition, *, base_packages='base base-devel linux linux-firmware efibootmgr nano', profile=None, mountpoint='/mnt', hostname='ArchInstalled', logdir=None, logfile=None):
+ def __init__(self, partition, boot_partition, *, base_packages=__base_packages__, profile=None, mountpoint='/mnt', hostname='ArchInstalled', logdir=None, logfile=None):
self.profile = profile
self.hostname = hostname
self.mountpoint = mountpoint
@@ -52,7 +56,7 @@ class Installer():
'user' : False # Root counts as a user, if additional users are skipped.
}
- self.base_packages = base_packages.split(' ')
+ self.base_packages = base_packages.split(' ') if type(base_packages) is str else base_packages
self.post_base_install = []
storage['session'] = self
@@ -133,7 +137,7 @@ class Installer():
self.log(f'Installing packages: {packages}', level=LOG_LEVELS.Info)
if (sync_mirrors := sys_command('/usr/bin/pacman -Syy')).exit_code == 0:
- if (pacstrap := sys_command(f'/usr/bin/pacstrap {self.mountpoint} {" ".join(packages)}', **kwargs)).exit_code == 0:
+ if (pacstrap := sys_command(f'/usr/bin/pacstrap {self.mountpoint} {" ".join(packages)}', peak_output=True, **kwargs)).exit_code == 0:
return True
else:
self.log(f'Could not strap in packages: {pacstrap.exit_code}', level=LOG_LEVELS.Info)
@@ -388,18 +392,10 @@ class Installer():
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={self.partition.uuid} rw intel_pstate=no_hwp\n')
- entry.write(f'options root=PARTUUID={uid} rw intel_pstate=no_hwp\n')
-
- self.helper_flags['bootloader'] = bootloader
- return True
- break
+ self.helper_flags['bootloader'] = bootloader
+ return True
raise RequirementError(f"Could not identify the UUID of {self.partition}, there for {self.mountpoint}/boot/loader/entries/arch.conf will be broken until fixed.")
elif bootloader == "grub-install":
diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py
index 19c21795..62067ec1 100644
--- a/archinstall/lib/luks.py
+++ b/archinstall/lib/luks.py
@@ -1,4 +1,5 @@
import os
+import shlex
from .exceptions import *
from .general import *
from .disk import Partition
@@ -64,9 +65,23 @@ class luks2():
with open(key_file, 'wb') as fh:
fh.write(password)
+ cryptsetup_args = shlex.join([
+ '/usr/bin/cryptsetup',
+ '--batch-mode',
+ '--verbose',
+ '--type', 'luks2',
+ '--pbkdf', 'argon2i',
+ '--hash', hash_type,
+ '--key-size', str(key_size),
+ '--iter-time', str(iter_time),
+ '--key-file', os.path.abspath(key_file),
+ '--use-urandom',
+ 'luksFormat', partition.path,
+ ])
+
try:
# 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}')
+ 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)
@@ -90,12 +105,12 @@ class luks2():
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}')
+ cmd_handle = sys_command(cryptsetup_args)
else:
raise err
- if b'Command successful.' not in b''.join(cmd_handle):
- raise DiskError(f'Could not encrypt volume "{partition.path}": {o}')
+ if cmd_handle.exit_code != 0:
+ raise DiskError(f'Could not encrypt volume "{partition.path}": {cmd_output}')
return key_file
@@ -126,4 +141,4 @@ class luks2():
def format(self, path):
if (handle := sys_command(f"/usr/bin/cryptsetup -q -v luksErase {path}")).exit_code != 0:
- raise DiskError(f'Could not format {path} with {self.filesystem} because: {b"".join(handle)}') \ No newline at end of file
+ raise DiskError(f'Could not format {path} with {self.filesystem} because: {b"".join(handle)}')
diff --git a/archinstall/lib/profiles.py b/archinstall/lib/profiles.py
index 77c9c6b2..70c21a67 100644
--- a/archinstall/lib/profiles.py
+++ b/archinstall/lib/profiles.py
@@ -193,6 +193,28 @@ class Profile(Script):
if hasattr(imported, '_post_install'):
return True
+ @property
+ def packages(self) -> list:
+ """
+ Returns a list of packages baked into the profile definition.
+ If no package definition has been done, .packages() will return None.
+ """
+ with open(self.path, 'r') as source:
+ source_data = source.read()
+
+ # Some crude safety checks, make sure the imported profile has
+ # a __name__ check before importing.
+ #
+ # If the requirements are met, import with .py in the namespace to not
+ # trigger a traditional:
+ # if __name__ == 'moduleName'
+ if '__name__' in source_data and '__packages__' in source_data:
+ with self.load_instructions(namespace=f"{self.namespace}.py") as imported:
+ if hasattr(imported, '__packages__'):
+ return imported.__packages__
+ return None
+
+
class Application(Profile):
def __repr__(self, *args, **kwargs):
return f'Application({os.path.basename(self.profile)})'
diff --git a/archinstall/lib/systemd.py b/archinstall/lib/systemd.py
index edd75098..f2b7c9b3 100644
--- a/archinstall/lib/systemd.py
+++ b/archinstall/lib/systemd.py
@@ -26,15 +26,11 @@ class Ini():
return result
class Systemd(Ini):
- def __init__(self, *args, **kwargs):
- """
- Placeholder class to do systemd specific setups.
- """
- super(Systemd, self).__init__(*args, **kwargs)
+ """
+ Placeholder class to do systemd specific setups.
+ """
class Networkd(Systemd):
- def __init__(self, *args, **kwargs):
- """
- Placeholder class to do systemd-network specific setups.
- """
- super(Networkd, self).__init__(*args, **kwargs) \ No newline at end of file
+ """
+ Placeholder class to do systemd-network specific setups.
+ """
diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py
index f8b4d9c5..58f88bd2 100644
--- a/archinstall/lib/user_interaction.py
+++ b/archinstall/lib/user_interaction.py
@@ -274,9 +274,16 @@ def select_language(options, show_only_country_codes=True):
print(' -- You can enter ? or help to search for more languages --')
selected_language = input('Select one of the above keyboard languages (by number or full name): ')
if selected_language.lower() in ('?', 'help'):
- filter_string = input('Search for layout containing (example: "sv-"): ')
- new_options = search_keyboard_layout(filter_string)
- return select_language(new_options, show_only_country_codes=False)
+ while True:
+ filter_string = input('Search for layout containing (example: "sv-"): ')
+ 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 or Ctrl+D to abort.", fg='yellow')
+ 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]
# I'm leaving "options" on purpose here.