Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--archinstall/__init__.py5
-rw-r--r--archinstall/lib/disk.py14
-rw-r--r--archinstall/lib/general.py52
-rw-r--r--archinstall/lib/installer.py19
-rw-r--r--archinstall/lib/luks.py4
-rw-r--r--archinstall/lib/profiles.py16
-rw-r--r--examples/guided_installation.py21
7 files changed, 96 insertions, 35 deletions
diff --git a/archinstall/__init__.py b/archinstall/__init__.py
index 9cf7faec..a9eeb6b5 100644
--- a/archinstall/__init__.py
+++ b/archinstall/__init__.py
@@ -4,4 +4,7 @@ from .lib.user_interaction import *
from .lib.exceptions import *
from .lib.installer import *
from .lib.profiles import *
-from .lib.luks import * \ No newline at end of file
+from .lib.luks import *
+
+if __name__ == '__main__':
+ print('Launching as a module?') \ No newline at end of file
diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py
index 1bdff8e2..30a7c44e 100644
--- a/archinstall/lib/disk.py
+++ b/archinstall/lib/disk.py
@@ -1,7 +1,7 @@
import glob, re, os, json
from collections import OrderedDict
from .exceptions import *
-from .general import sys_command
+from .general import *
ROOT_DIR_PATTERN = re.compile('^.*?/devices')
GPT = 0b00000001
@@ -90,7 +90,7 @@ class Partition():
return f'Partition({self.path}, fs={self.filesystem}, mounted={self.mountpoint})'
def format(self, filesystem):
- print(f'Formatting {self} -> {filesystem}')
+ log(f'Formatting {self} -> {filesystem}')
if filesystem == 'btrfs':
o = b''.join(sys_command(f'/usr/bin/mkfs.btrfs -f {self.path}'))
if not b'UUID' in o:
@@ -101,13 +101,17 @@ class Partition():
if (b'mkfs.fat' not in o and b'mkfs.vfat' not in o) or b'command not found' in o:
raise DiskError(f'Could not format {self.path} with {filesystem} because: {o}')
self.filesystem = 'fat32'
+ elif filesystem == 'ext4':
+ if (handle := sys_command(f'/usr/bin/mkfs.ext4 -F {self.path}')).exit_code != 0:
+ raise DiskError(f'Could not format {self.path} with {filesystem} because: {b"".join(handle)}')
+ self.filesystem = 'fat32'
else:
raise DiskError(f'Fileformat {filesystem} is not yet implemented.')
return True
def mount(self, target, fs=None, options=''):
if not self.mountpoint:
- print(f'Mounting {self} to {target}')
+ log(f'Mounting {self} to {target}')
if not fs:
if not self.filesystem: raise DiskError(f'Need to format (or define) the filesystem on {self} before mounting.')
fs = self.filesystem
@@ -166,10 +170,10 @@ class Filesystem():
if prep_mode == 'luks2':
self.add_partition('primary', start='513MiB', end='100%')
else:
- self.add_partition('primary', start='513MiB', end='513MiB', format='ext4')
+ self.add_partition('primary', start='513MiB', end='100%', format='ext4')
def add_partition(self, type, start, end, format=None):
- print(f'Adding partition to {self.blockdevice}')
+ log(f'Adding partition to {self.blockdevice}')
if format:
return self.parted(f'{self.blockdevice.device} mkpart {type} {format} {start} {end}') == 0
else:
diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py
index 89c7f188..88cfc047 100644
--- a/archinstall/lib/general.py
+++ b/archinstall/lib/general.py
@@ -1,10 +1,14 @@
-import os, json, hashlib, shlex
+import os, json, hashlib, shlex, sys
import time, pty
from subprocess import Popen, STDOUT, PIPE, check_output
from select import epoll, EPOLLIN, EPOLLHUP
def log(*args, **kwargs):
- print(' '.join([str(x) for x in args]))
+ string = ' '.join([str(x) for x in args])
+ if supports_color():
+ kwargs = {'bg' : 'black', 'fg': 'white', **kwargs}
+ string = stylize_output(string, **kwargs)
+ print(string)
def gen_uid(entropy_length=256):
return hashlib.sha512(os.urandom(entropy_length)).hexdigest()
@@ -23,6 +27,43 @@ def multisplit(s, splitters):
s = ns
return s
+# Heavily influenced by: https://github.com/django/django/blob/ae8338daf34fd746771e0678081999b656177bae/django/utils/termcolors.py#L13
+# Color options here: https://askubuntu.com/questions/528928/how-to-do-underline-bold-italic-strikethrough-color-background-and-size-i
+def stylize_output(text :str, *opts, **kwargs):
+ opt_dict = {'bold': '1', 'italic' : '3', 'underscore': '4', 'blink': '5', 'reverse': '7', 'conceal': '8'}
+ color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white')
+ foreground = {color_names[x]: '3%s' % x for x in range(8)}
+ background = {color_names[x]: '4%s' % x for x in range(8)}
+ RESET = '0'
+
+ code_list = []
+ if text == '' and len(opts) == 1 and opts[0] == 'reset':
+ return '\x1b[%sm' % RESET
+ for k, v in kwargs.items():
+ if k == 'fg':
+ code_list.append(foreground[v])
+ elif k == 'bg':
+ code_list.append(background[v])
+ for o in opts:
+ if o in opt_dict:
+ code_list.append(opt_dict[o])
+ if 'noreset' not in opts:
+ text = '%s\x1b[%sm' % (text or '', RESET)
+ return '%s%s' % (('\x1b[%sm' % ';'.join(code_list)), text or '')
+
+# Found first reference here: https://stackoverflow.com/questions/7445658/how-to-detect-if-the-console-does-support-ansi-escape-codes-in-python
+# And re-used this: https://github.com/django/django/blob/master/django/core/management/color.py#L12
+def supports_color():
+ """
+ Return True if the running system's terminal supports color,
+ and False otherwise.
+ """
+ supported_platform = sys.platform != 'win32' or 'ANSICON' in os.environ
+
+ # isatty is not always implemented, #6223.
+ is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
+ return supported_platform and is_a_tty
+
class sys_command():#Thread):
"""
Stolen from archinstall_gui
@@ -165,14 +206,14 @@ class sys_command():#Thread):
if bytes(f']$'.lower(), 'UTF-8') in self.trace_log[0-len(f']$')-5:].lower():
if 'debug' in self.kwargs and self.kwargs['debug']:
- log(f"{self.cmd[0]} has finished.", origin='spawn', level=4)
+ log(f"{self.cmd[0]} has finished.")
alive = False
break
self.status = 'done'
if 'debug' in self.kwargs and self.kwargs['debug']:
- log(f"{self.cmd[0]} waiting for exit code.", origin='spawn', level=5)
+ log(f"{self.cmd[0]} waiting for exit code.")
if not self.kwargs['emulate']:
try:
@@ -185,6 +226,9 @@ class sys_command():#Thread):
else:
self.exit_code = 0
+ if 'debug' in self.kwargs and self.kwargs['debug']:
+ log(f"{self.cmd[0]} got exit code: {self.exit_code}")
+
if 'ignore_errors' in self.kwargs:
self.exit_code = 0
diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py
index d804818a..d3e6c381 100644
--- a/archinstall/lib/installer.py
+++ b/archinstall/lib/installer.py
@@ -23,26 +23,26 @@ 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]:
raise args[1]
- print('Installation completed without any errors.')
+ log('Installation completed without any errors.', bg='black', fg='green')
return True
def pacstrap(self, *packages):
if type(packages[0]) in (list, tuple): packages = packages[0]
- print(f'Installing packages: {packages}')
+ log(f'Installing packages: {packages}')
if (sync_mirrors := sys_command('/usr/bin/pacman -Syy')).exit_code == 0:
if (pacstrap := sys_command(f'/usr/bin/pacstrap {self.mountpoint} {" ".join(packages)}')).exit_code == 0:
return True
else:
- print(f'Could not strap in packages: {pacstrap.exit_code}')
+ log(f'Could not strap in packages: {pacstrap.exit_code}')
else:
- print(f'Could not sync mirrors: {sync_mirrors.exit_code}')
+ log(f'Could not sync mirrors: {sync_mirrors.exit_code}')
def minimal_installation(self):
return self.pacstrap('base base-devel linux linux-firmware btrfs-progs efibootmgr nano wpa_supplicant dialog'.split(' '))
def add_bootloader(self, partition):
- print(f'Adding bootloader to {partition}')
+ log(f'Adding bootloader to {partition}')
os.makedirs(f'{self.mountpoint}/boot', exist_ok=True)
partition.mount(f'{self.mountpoint}/boot')
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} bootctl --no-variables --path=/boot install'))
@@ -78,11 +78,11 @@ class Installer():
def install_profile(self, profile):
profile = Profile(self, profile)
- print(f'Installing network profile {profile}')
+ log(f'Installing network profile {profile}')
profile.install()
def user_create(self, user :str, password=None, groups=[]):
- print(f'Creating user {user}')
+ log(f'Creating user {user}')
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} useradd -m -G wheel {user}'))
if password:
self.user_set_pw(user, password)
@@ -91,12 +91,13 @@ class Installer():
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} gpasswd -a {user} {group}'))
def user_set_pw(self, user, password):
- print(f'Setting password for {user}')
+ log(f'Setting password for {user}')
o = b''.join(sys_command(f"/usr/bin/arch-chroot {self.mountpoint} sh -c \"echo '{user}:{password}' | chpasswd\""))
pass
def add_AUR_support(self):
- print(f'Building and installing yay support into {self.mountpoint}')
+ log(f'Building and installing yay support into {self.mountpoint}')
+ self.add_additional_packages(['git', 'base-devel']) # TODO: Remove if not explicitly added at one point
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} sh -c "useradd -m -G wheel aibuilder"'))
o = b''.join(sys_command(f"/usr/bin/sed -i 's/# %wheel ALL=(ALL) NO/%wheel ALL=(ALL) NO/' {self.mountpoint}/etc/sudoers"))
diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py
index 707eeeab..71e634e1 100644
--- a/archinstall/lib/luks.py
+++ b/archinstall/lib/luks.py
@@ -1,6 +1,6 @@
import os
from .exceptions import *
-from .general import sys_command
+from .general import *
from .disk import Partition
class luks2():
@@ -22,7 +22,7 @@ class luks2():
return True
def encrypt(self, partition, password, key_size=512, hash_type='sha512', iter_time=10000, key_file=None):
- print(f'Encrypting {partition}')
+ log(f'Encrypting {partition}')
if not key_file: key_file = f'/tmp/{os.path.basename(self.partition.path)}.disk_pw' #TODO: Make disk-pw-file randomly unique?
if type(password) != bytes: password = bytes(password, 'UTF-8')
diff --git a/archinstall/lib/profiles.py b/archinstall/lib/profiles.py
index bea17d44..83f217d5 100644
--- a/archinstall/lib/profiles.py
+++ b/archinstall/lib/profiles.py
@@ -35,23 +35,23 @@ def get_application_instructions(target):
try:
instructions = grab_url_data(f'{UPSTREAM_URL}/applications/{target}.json').decode('UTF-8')
- print('[N] Found application instructions for: {}'.format(target))
+ log('[N] Found application instructions for: {}'.format(target))
except urllib.error.HTTPError:
- print('[N] Could not find remote instructions. yrying local instructions under ./profiles/applications')
+ log('[N] Could not find remote instructions. yrying local instructions under ./profiles/applications')
local_path = './profiles/applications' if os.path.isfile('./archinstall.py') else './archinstall/profiles/applications' # Dangerous assumption
if os.path.isfile(f'{local_path}/{target}.json'):
with open(f'{local_path}/{target}.json', 'r') as fh:
instructions = fh.read()
- print('[N] Found local application instructions for: {}'.format(target))
+ log('[N] Found local application instructions for: {}'.format(target))
else:
- print('[N] No instructions found for: {}'.format(target))
+ log('[N] No instructions found for: {}'.format(target))
return instructions
try:
instructions = json.loads(instructions, object_pairs_hook=oDict)
except:
- print('[E] JSON syntax error in {}'.format('{}/applications/{}.json'.format(args['profiles-path'], target)))
+ log('[E] JSON syntax error in {}'.format('{}/applications/{}.json'.format(args['profiles-path'], target)))
traceback.print_exc()
exit(1)
@@ -108,9 +108,9 @@ class Profile():
for title in instructions:
log(f'Running post installation step {title}')
- print('[N] Network Deploy: {}'.format(title))
+ log('[N] Network Deploy: {}'.format(title))
if type(instructions[title]) == str:
- print('[N] Loading {} configuration'.format(instructions[title]))
+ log('[N] Loading {} configuration'.format(instructions[title]))
log(f'Loading {instructions[title]} configuration')
instructions[title] = Application(self.installer, instructions[title], args=self.args)
instructions[title].install()
@@ -170,7 +170,7 @@ class Profile():
o = b''.join(sys_command(f'/usr/bin/systemd-nspawn -D {self.installer.mountpoint} --machine temporary {command}'))
if type(instructions[title][raw_command]) == bytes and len(instructions['post'][title][raw_command]) and not instructions['post'][title][raw_command] in o:
log(f'{command} failed: {o.decode("UTF-8")}')
- print('[W] Post install command failed: {}'.format(o.decode('UTF-8')))
+ log('[W] Post install command failed: {}'.format(o.decode('UTF-8')))
class Application(Profile):
@property
diff --git a/examples/guided_installation.py b/examples/guided_installation.py
index 4364cb26..c84b5e56 100644
--- a/examples/guided_installation.py
+++ b/examples/guided_installation.py
@@ -6,7 +6,12 @@ archinstall.sys_command(f'cryptsetup close /dev/mapper/luksloop', surpress_error
# Select a harddrive and a disk password
harddrive = archinstall.select_disk(archinstall.all_disks())
-disk_password = getpass.getpass(prompt='Disk password (If empty, won\'t use disk encryption): ')
+while (disk_password := getpass.getpass(prompt='Enter disk encryption password (leave blank for no encryption): ')):
+ disk_password_verification = getpass.getpass(prompt='And one more time for verification: ')
+ if disk_password != disk_password_verification:
+ archinstall.log(' * Passwords did not match * ', bg='black', fg='red')
+ continue
+ break
def perform_installation(device, boot_partition):
hostname = input('Desired hostname for the installation: ')
@@ -15,7 +20,7 @@ def perform_installation(device, boot_partition):
installation.add_bootloader(boot_partition)
packages = input('Additional packages aside from base (space separated): ').split(' ')
- if len(packages):
+ if len(packages) and packages[0] != '':
installation.add_additional_packages(packages)
profile = input('Any particular profile you want to install: ')
@@ -28,18 +33,22 @@ def perform_installation(device, boot_partition):
new_user_passwd = getpass.getpass(prompt=f'Password for user {new_user}: ')
new_user_passwd_verify = getpass.getpass(prompt=f'Enter password again for verification: ')
if new_user_passwd != new_user_passwd_verify:
- print(' * Passwords did not match * ')
+ archinstall.log(' * Passwords did not match * ', bg='black', fg='red')
continue
installation.user_create(new_user, new_user_passwd)
- root_pw = getpass.getpass(prompt='Enter root password: ')
- if len(root_pw.strip()):
+ while (root_pw := getpass.getpass(prompt='Enter root password (leave blank for no password): ')):
+ root_pw_verification = getpass.getpass(prompt='And one more time for verification: ')
+ if root_pw != root_pw_verification:
+ archinstall.log(' * Passwords did not match * ', bg='black', fg='red')
+ continue
installation.user_set_pw('root', root_pw)
+ break
aur = input('Would you like AUR support? (leave blank for no): ')
if len(aur.strip()):
- print(' - AUR support provided by yay (https://aur.archlinux.org/packages/yay/)')
+ archinstall.log(' - AUR support provided by yay (https://aur.archlinux.org/packages/yay/)', bg='black', fg='white')
installation.add_AUR_support()
with archinstall.Filesystem(harddrive, archinstall.GPT) as fs: