From 0bc24699c1aba583b1d98809321e2f726425f3fe Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 7 Jul 2020 21:59:09 +0000 Subject: Added colored output. Also tested non-encrypted installations and added ext4 support. --- archinstall/lib/disk.py | 14 +++++++----- archinstall/lib/general.py | 52 ++++++++++++++++++++++++++++++++++++++++---- archinstall/lib/installer.py | 19 ++++++++-------- archinstall/lib/luks.py | 4 ++-- archinstall/lib/profiles.py | 16 +++++++------- 5 files changed, 77 insertions(+), 28 deletions(-) (limited to 'archinstall/lib') 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 -- cgit v1.2.3-54-g00ecf