From cf865c98b59562f6cf8e1761b0a925aa1c01ab1f Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 18 Nov 2019 23:12:36 +0000 Subject: Finished moving almost all steps into functions. --- archinstall.py | 337 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 184 insertions(+), 153 deletions(-) diff --git a/archinstall.py b/archinstall.py index 7067d5ec..d0bd23b8 100644 --- a/archinstall.py +++ b/archinstall.py @@ -502,7 +502,7 @@ def disk_info(drive, *positionals, **kwargs): lkwargs = {**kwargs} lkwargs['emulate'] = False # This is a emulate-safe function. Does not alter filesystem. - info = json.loads(b''.join(sys_command(f'lsblk -J -o "NAME,SIZE,FSTYPE,LABEL" {drive}', *positionals, **lkwargs)).decode('UTF_8'))['blockdevices'][0] + info = json.loads(b''.join(sys_command(f'lsblk -J -o "NAME,SIZE,FSTYPE,LABEL" {drive}', *positionals, **lkwargs, hide_from_log=True)).decode('UTF_8'))['blockdevices'][0] fileformats = [] labels = [] if 'children' in info: ## Might not be partitioned yet @@ -809,7 +809,7 @@ def mkfs_fat32(drive, partition, *positionals, **kwargs): return True def is_luksdev_mounted(*positionals, **kwargs): - o = b''.join(sys_command('/usr/bin/file /dev/mapper/luksdev')) # /dev/dm-0 + o = b''.join(sys_command('/usr/bin/file /dev/mapper/luksdev', hide_from_log=True)) # /dev/dm-0 if b'cannot open' in o: return False return True @@ -857,6 +857,182 @@ def mount_mountpoints(drive, bootpartition, mountpoint='/mnt/boot', *positionals mount_boot(drive, bootpartition, mountpoint='/mnt/boot', *positionals, **kwargs) return True +def filter_mirrors_by_country(countries, top=10, *positionals, **kwargs): + ## TODO: replace wget with urllib.request (no point in calling syscommand) + country_list = [] + for country in countries.split(','): + country_list.append(f'country={country}') + o = simple_command(f"/usr/bin/wget 'https://www.archlinux.org/mirrorlist/?{'&'.join(country_list)}&protocol=https&ip_version=4&ip_version=6&use_mirror_status=on' -O /root/mirrorlist") + o = simple_command("/usr/bin/sed -i 's/#Server/Server/' /root/mirrorlist") + o = simple_command('/usr/bin/rankmirrors -n {top} /root/mirrorlist > /etc/pacman.d/mirrorlist') + return True + +def strap_in_base(*positionals, **kwargs): + if args['aur-support']: + args['packages'] += ' git' + if sys_command('/usr/bin/pacman -Syy').exit_code == 0: + if sys_command('/usr/bin/pacstrap /mnt base base-devel linux linux-firmware btrfs-progs efibootmgr nano wpa_supplicant dialog {packages}'.format(**args)).exit_code == 0: + return True + return False + +def configure_base_system(*positionals, **kwargs): + ## TODO: Replace a lot of these syscalls with just python native operations. + o = b''.join(sys_command('/usr/bin/genfstab -pU /mnt >> /mnt/etc/fstab')) + with open('/mnt/etc/fstab', 'a') as fstab: + fstab.write('\ntmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0\n') # Redundant \n at the start? who knoes? + + o = b''.join(sys_command('/usr/bin/arch-chroot /mnt rm -f /etc/localtime')) + o = b''.join(sys_command('/usr/bin/arch-chroot /mnt ln -s /usr/share/zoneinfo/{localtime} /etc/localtime'.format(**args))) + o = b''.join(sys_command('/usr/bin/arch-chroot /mnt hwclock --hctosys --localtime')) + #o = sys_command('arch-chroot /mnt echo "{hostname}" > /etc/hostname'.format(**args)) + #o = sys_command("arch-chroot /mnt sed -i 's/#\(en_US\.UTF-8\)/\1/' /etc/locale.gen") + o = b''.join(sys_command("/usr/bin/arch-chroot /mnt sh -c \"echo '{hostname}' > /etc/hostname\"".format(**args))) + o = b''.join(sys_command("/usr/bin/arch-chroot /mnt sh -c \"echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen\"")) + o = b''.join(sys_command("/usr/bin/arch-chroot /mnt sh -c \"echo 'LANG=en_US.UTF-8' > /etc/locale.conf\"")) + o = b''.join(sys_command('/usr/bin/arch-chroot /mnt locale-gen')) + o = b''.join(sys_command('/usr/bin/arch-chroot /mnt chmod 700 /root')) + + with open('/mnt/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('BINARIES=(/usr/bin/btrfs)\n') + mkinit.write('FILES=()\n') + mkinit.write('HOOKS=(base udev autodetect modconf block encrypt filesystems keyboard fsck)\n') + o = b''.join(sys_command('/usr/bin/arch-chroot /mnt mkinitcpio -p linux')) + + return True + +def setup_bootloader(*positionals, **kwargs): + o = b''.join(sys_command('/usr/bin/arch-chroot /mnt bootctl --no-variables --path=/boot install')) + + with open('/mnt/boot/loader/loader.conf', 'w') as loader: + loader.write('default arch\n') + loader.write('timeout 5\n') + + ## For some reason, blkid and /dev/disk/by-uuid are not getting along well. + ## And blkid is wrong in terms of LUKS. + #UUID = sys_command('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip() + UUID = simple_command(f"ls -l /dev/disk/by-uuid/ | grep {os.path.basename(args['drive'])}{args['partitions']['2']} | awk '{{print $9}}'").decode('UTF-8').strip() + with open('/mnt/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') + entry.write('options cryptdevice=UUID={UUID}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n'.format(UUID=UUID)) + + return True + +def add_AUR_support(*positionals, **kwargs): + o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "useradd -m -G wheel aibuilder"')) + o = b''.join(sys_command("/usr/bin/sed -i 's/# %wheel ALL=(ALL) NO/%wheel ALL=(ALL) NO/' /mnt/etc/sudoers")) + + o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "su - aibuilder -c \\"(cd /home/aibuilder; git clone https://aur.archlinux.org/yay.git)\\""')) + o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "chown -R aibuilder.aibuilder /home/aibuilder/yay"')) + o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "su - aibuilder -c \\"(cd /home/aibuilder/yay; makepkg -si --noconfirm)\\" >/dev/null"')) + ## Do not remove aibuilder just yet, can be used later for aur packages. + #o = b''.join(sys_command('/usr/bin/sed -i \'s/%wheel ALL=(ALL) NO/# %wheel ALL=(ALL) NO/\' /mnt/etc/sudoers')) + #o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "userdel aibuilder"')) + #o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "rm -rf /home/aibuilder"')) + return True + +def run_post_install_steps(*positionals, **kwargs): + conf = {} + if 'post' in instructions: + conf = instructions['post'] + elif not 'args' in instructions and len(instructions): + conf = instructions + + if 'git-branch' in conf: + update_git(conf['git-branch']) + del(conf['git-branch']) + + for title in conf: + if args['rerun'] and args['rerun'] != title and not rerun: + continue + else: + rerun = True + + print('[N] Network Deploy: {}'.format(title)) + if type(conf[title]) == str: + print('[N] Loading {} configuration'.format(conf[title])) + conf[title] = get_application_instructions(conf[title]) + + for command in conf[title]: + raw_command = command + opts = conf[title][command] if type(conf[title][command]) in (dict, oDict) else {} + if len(opts): + if 'pass-args' in opts or 'format' in opts: + command = command.format(**args) + ## FIXME: Instead of deleting the two options + ## in order to mute command output further down, + ## check for a 'debug' flag per command and delete these two + if 'pass-args' in opts: + del(opts['pass-args']) + elif 'format' in opts: + del(opts['format']) + elif ('debug' in opts and opts['debug']) or ('debug' in conf and conf['debug']): + print('[-] Options: {}'.format(opts)) + if 'pass-args' in opts and opts['pass-args']: + command = command.format(**args) + + if 'runas' in opts and f'su - {opts["runas"]} -c' not in command: + command = command.replace('"', '\\"') + command = f'su - {opts["runas"]} -c "{command}"' + + #print('[N] Command: {} ({})'.format(command, opts)) + + ## https://superuser.com/questions/1242978/start-systemd-nspawn-and-execute-commands-inside + ## !IMPORTANT + ## + ## arch-chroot mounts /run into the chroot environment, this breaks name resolves for some reason. + ## Either skipping mounting /run and using traditional chroot is an option, but using + ## `systemd-nspawn -D /mnt --machine temporary` might be a more flexible solution in case of file structure changes. + if 'no-chroot' in opts and opts['no-chroot']: + o = simple_command(command, opts) + elif 'chroot' in opts and opts['chroot']: + ## Run in a manually set up version of arch-chroot (arch-chroot will break namespaces). + ## This is a bit risky in case the file systems changes over the years, but we'll probably be safe adding this as an option. + ## **> Prefer if possible to use 'no-chroot' instead which "live boots" the OS and runs the command. + o = simple_command("mount /dev/mapper/luksdev /mnt") + o = simple_command("cd /mnt; cp /etc/resolv.conf etc") + o = simple_command("cd /mnt; mount -t proc /proc proc") + o = simple_command("cd /mnt; mount --make-rslave --rbind /sys sys") + o = simple_command("cd /mnt; mount --make-rslave --rbind /dev dev") + o = simple_command('chroot /mnt /bin/bash -c "{c}"'.format(c=command), opts=opts) + o = simple_command("cd /mnt; umount -R dev") + o = simple_command("cd /mnt; umount -R sys") + o = simple_command("cd /mnt; umount -R proc") + else: + if 'boot' in opts and opts['boot']: + ## So, if we're going to boot this maddafakker up, we'll need to + ## be able to login. The quickest way is to just add automatic login.. so lessgo! + + ## Turns out.. that didn't work exactly as planned.. + ## + # if not os.path.isdir('/mnt/etc/systemd/system/console-getty.service.d/'): + # os.makedirs('/mnt/etc/systemd/system/console-getty.service.d/') + # with open('/mnt/etc/systemd/system/console-getty.service.d/override.conf', 'w') as fh: + # fh.write('[Service]\n') + # fh.write('ExecStart=\n') + # fh.write('ExecStart=-/usr/bin/agetty --autologin root -s %I 115200,38400,9600 vt102\n') + + ## So we'll add a bunch of triggers instead and let the sys_command manually react to them. + ## " login" followed by "Passwodd" in case it's been set in a previous step.. usually this shouldn't be nessecary + ## since we set the password as the last step. And then the command itself which will be executed by looking for: + ## [root@ ~]# + o = b''.join(sys_command('/usr/bin/systemd-nspawn -D /mnt -b --machine temporary', opts={'triggers' : { + bytes(f'login:', 'UTF-8') : b'root\n', + #b'Password:' : bytes(args['password']+'\n', 'UTF-8'), + bytes(f'[root@{args["hostname"]} ~]#', 'UTF-8') : bytes(command+'\n', 'UTF-8'), + }, **opts})) + + ## Not needed anymore: And cleanup after out selves.. Don't want to leave any residue.. + # os.remove('/mnt/etc/systemd/system/console-getty.service.d/override.conf') + else: + o = b''.join(sys_command('/usr/bin/systemd-nspawn -D /mnt --machine temporary {c}'.format(c=command), opts=opts)) + if type(conf[title][raw_command]) == bytes and len(conf[title][raw_command]) and not conf[title][raw_command] in o: + print('[W] Post install command failed: {}'.format(o.decode('UTF-8'))) + #print(o) + if __name__ == '__main__': update_git() # Breaks and restarts the script if an update was found. update_drive_list() @@ -948,9 +1124,7 @@ if __name__ == '__main__': if 'mirrors' in args and args['mirrors'] and 'country' in args and get_default_gateway_linux(): print('[N] Reordering mirrors.') - o = simple_command("/usr/bin/wget 'https://www.archlinux.org/mirrorlist/?country={country}&protocol=https&ip_version=4&ip_version=6&use_mirror_status=on' -O /root/mirrorlist".format(**args)) - o = simple_command("/usr/bin/sed -i 's/#Server/Server/' /root/mirrorlist") - o = simple_command('/usr/bin/rankmirrors -n 6 /root/mirrorlist > /etc/pacman.d/mirrorlist') + filter_mirrors_by_country(args['country']) pre_conf = {} if 'pre' in instructions: @@ -999,166 +1173,23 @@ if __name__ == '__main__': if not args['rerun'] or rerun: print('[N] Straping in packages.') - if args['aur-support']: - args['packages'] += ' git' - o = b''.join(sys_command('/usr/bin/pacman -Syy')) - o = b''.join(sys_command('/usr/bin/pacstrap /mnt base base-devel linux linux-firmware btrfs-progs efibootmgr nano wpa_supplicant dialog {packages}'.format(**args))) + strap_in_base() # TODO: check return here? we return based off pacstrap exit code.. Never tired it tho. if not os.path.isdir('/mnt/etc'): # TODO: This might not be the most long term stable thing to rely on... print('[E] Failed to strap in packages', o) exit(1) if not args['rerun'] or rerun: - o = b''.join(sys_command('/usr/bin/genfstab -pU /mnt >> /mnt/etc/fstab')) - with open('/mnt/etc/fstab', 'a') as fstab: - fstab.write('\ntmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0\n') # Redundant \n at the start? who knoes? - - o = b''.join(sys_command('/usr/bin/arch-chroot /mnt rm -f /etc/localtime')) - o = b''.join(sys_command('/usr/bin/arch-chroot /mnt ln -s /usr/share/zoneinfo/{localtime} /etc/localtime'.format(**args))) - o = b''.join(sys_command('/usr/bin/arch-chroot /mnt hwclock --hctosys --localtime')) - #o = sys_command('arch-chroot /mnt echo "{hostname}" > /etc/hostname'.format(**args)) - #o = sys_command("arch-chroot /mnt sed -i 's/#\(en_US\.UTF-8\)/\1/' /etc/locale.gen") - o = b''.join(sys_command("/usr/bin/arch-chroot /mnt sh -c \"echo '{hostname}' > /etc/hostname\"".format(**args))) - o = b''.join(sys_command("/usr/bin/arch-chroot /mnt sh -c \"echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen\"")) - o = b''.join(sys_command("/usr/bin/arch-chroot /mnt sh -c \"echo 'LANG=en_US.UTF-8' > /etc/locale.conf\"")) - o = b''.join(sys_command('/usr/bin/arch-chroot /mnt locale-gen')) - o = b''.join(sys_command('/usr/bin/arch-chroot /mnt chmod 700 /root')) - - with open('/mnt/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('BINARIES=(/usr/bin/btrfs)\n') - mkinit.write('FILES=()\n') - mkinit.write('HOOKS=(base udev autodetect modconf block encrypt filesystems keyboard fsck)\n') - o = b''.join(sys_command('/usr/bin/arch-chroot /mnt mkinitcpio -p linux')) + configure_base_system() ## WORKAROUND: https://github.com/systemd/systemd/issues/13603#issuecomment-552246188 - o = b''.join(sys_command('/usr/bin/arch-chroot /mnt bootctl --no-variables --path=/boot install')) - - with open('/mnt/boot/loader/loader.conf', 'w') as loader: - loader.write('default arch\n') - loader.write('timeout 5\n') - - ## For some reason, blkid and /dev/disk/by-uuid are not getting along well. - ## And blkid is wrong in terms of LUKS. - #UUID = sys_command('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip() - UUID = simple_command(f"ls -l /dev/disk/by-uuid/ | grep {os.path.basename(args['drive'])}{args['partitions']['2']} | awk '{{print $9}}'").decode('UTF-8').strip() - with open('/mnt/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') - entry.write('options cryptdevice=UUID={UUID}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n'.format(UUID=UUID)) + setup_bootloader() if args['aur-support']: print('[N] AUR support demanded, building "yay" before running POST steps.') - o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "useradd -m -G wheel aibuilder"')) - o = b''.join(sys_command("/usr/bin/sed -i 's/# %wheel ALL=(ALL) NO/%wheel ALL=(ALL) NO/' /mnt/etc/sudoers")) - - o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "su - aibuilder -c \\"(cd /home/aibuilder; git clone https://aur.archlinux.org/yay.git)\\""')) - o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "chown -R aibuilder.aibuilder /home/aibuilder/yay"')) - o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "su - aibuilder -c \\"(cd /home/aibuilder/yay; makepkg -si --noconfirm)\\" >/dev/null"')) - ## Do not remove aibuilder just yet, can be used later for aur packages. - #o = b''.join(sys_command('/usr/bin/sed -i \'s/%wheel ALL=(ALL) NO/# %wheel ALL=(ALL) NO/\' /mnt/etc/sudoers')) - #o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "userdel aibuilder"')) - #o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "rm -rf /home/aibuilder"')) + add_AUR_support() print('[N] AUR support added. use "yay -Syy --noconfirm " to deploy in POST.') - conf = {} - if 'post' in instructions: - conf = instructions['post'] - elif not 'args' in instructions and len(instructions): - conf = instructions - - if 'git-branch' in conf: - update_git(conf['git-branch']) - del(conf['git-branch']) - - for title in conf: - if args['rerun'] and args['rerun'] != title and not rerun: - continue - else: - rerun = True - - print('[N] Network Deploy: {}'.format(title)) - if type(conf[title]) == str: - print('[N] Loading {} configuration'.format(conf[title])) - conf[title] = get_application_instructions(conf[title]) - - for command in conf[title]: - raw_command = command - opts = conf[title][command] if type(conf[title][command]) in (dict, oDict) else {} - if len(opts): - if 'pass-args' in opts or 'format' in opts: - command = command.format(**args) - ## FIXME: Instead of deleting the two options - ## in order to mute command output further down, - ## check for a 'debug' flag per command and delete these two - if 'pass-args' in opts: - del(opts['pass-args']) - elif 'format' in opts: - del(opts['format']) - elif ('debug' in opts and opts['debug']) or ('debug' in conf and conf['debug']): - print('[-] Options: {}'.format(opts)) - if 'pass-args' in opts and opts['pass-args']: - command = command.format(**args) - - if 'runas' in opts and f'su - {opts["runas"]} -c' not in command: - command = command.replace('"', '\\"') - command = f'su - {opts["runas"]} -c "{command}"' - - #print('[N] Command: {} ({})'.format(command, opts)) - - ## https://superuser.com/questions/1242978/start-systemd-nspawn-and-execute-commands-inside - ## !IMPORTANT - ## - ## arch-chroot mounts /run into the chroot environment, this breaks name resolves for some reason. - ## Either skipping mounting /run and using traditional chroot is an option, but using - ## `systemd-nspawn -D /mnt --machine temporary` might be a more flexible solution in case of file structure changes. - if 'no-chroot' in opts and opts['no-chroot']: - o = simple_command(command, opts) - elif 'chroot' in opts and opts['chroot']: - ## Run in a manually set up version of arch-chroot (arch-chroot will break namespaces). - ## This is a bit risky in case the file systems changes over the years, but we'll probably be safe adding this as an option. - ## **> Prefer if possible to use 'no-chroot' instead which "live boots" the OS and runs the command. - o = simple_command("mount /dev/mapper/luksdev /mnt") - o = simple_command("cd /mnt; cp /etc/resolv.conf etc") - o = simple_command("cd /mnt; mount -t proc /proc proc") - o = simple_command("cd /mnt; mount --make-rslave --rbind /sys sys") - o = simple_command("cd /mnt; mount --make-rslave --rbind /dev dev") - o = simple_command('chroot /mnt /bin/bash -c "{c}"'.format(c=command), opts=opts) - o = simple_command("cd /mnt; umount -R dev") - o = simple_command("cd /mnt; umount -R sys") - o = simple_command("cd /mnt; umount -R proc") - else: - if 'boot' in opts and opts['boot']: - ## So, if we're going to boot this maddafakker up, we'll need to - ## be able to login. The quickest way is to just add automatic login.. so lessgo! - - ## Turns out.. that didn't work exactly as planned.. - ## - # if not os.path.isdir('/mnt/etc/systemd/system/console-getty.service.d/'): - # os.makedirs('/mnt/etc/systemd/system/console-getty.service.d/') - # with open('/mnt/etc/systemd/system/console-getty.service.d/override.conf', 'w') as fh: - # fh.write('[Service]\n') - # fh.write('ExecStart=\n') - # fh.write('ExecStart=-/usr/bin/agetty --autologin root -s %I 115200,38400,9600 vt102\n') - - ## So we'll add a bunch of triggers instead and let the sys_command manually react to them. - ## " login" followed by "Passwodd" in case it's been set in a previous step.. usually this shouldn't be nessecary - ## since we set the password as the last step. And then the command itself which will be executed by looking for: - ## [root@ ~]# - o = b''.join(sys_command('/usr/bin/systemd-nspawn -D /mnt -b --machine temporary', opts={'triggers' : { - bytes(f'login:', 'UTF-8') : b'root\n', - #b'Password:' : bytes(args['password']+'\n', 'UTF-8'), - bytes(f'[root@{args["hostname"]} ~]#', 'UTF-8') : bytes(command+'\n', 'UTF-8'), - }, **opts})) - - ## Not needed anymore: And cleanup after out selves.. Don't want to leave any residue.. - # os.remove('/mnt/etc/systemd/system/console-getty.service.d/override.conf') - else: - o = b''.join(sys_command('/usr/bin/systemd-nspawn -D /mnt --machine temporary {c}'.format(c=command), opts=opts)) - if type(conf[title][raw_command]) == bytes and len(conf[title][raw_command]) and not conf[title][raw_command] in o: - print('[W] Post install command failed: {}'.format(o.decode('UTF-8'))) - #print(o) + run_post_install_steps() if args['aur-support']: o = b''.join(sys_command('/usr/bin/sed -i \'s/%wheel ALL=(ALL) NO/# %wheel ALL=(ALL) NO/\' /mnt/etc/sudoers')) -- cgit v1.2.3-70-g09d2