Added colored output. Also tested non-encrypted installations and added ext4 support.
This commit is contained in:
parent
78d4fe4fa1
commit
0bc24699c1
|
|
@ -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 *
|
||||
from .lib.luks import *
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('Launching as a module?')
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in New Issue