(breaking changes) This fixes #124 and some more
This commit is contained in:
commit
afda647623
59
README.md
59
README.md
|
|
@ -21,6 +21,18 @@ Assuming you are on a Arch Linux live-ISO and booted into EFI mode.
|
|||
|
||||
# python -m archinstall guided
|
||||
|
||||
# Mission Statement
|
||||
|
||||
Archinstall promises to ship a [guided installer](https://github.com/archlinux/archinstall/blob/master/examples/guided.py) that follows the [Arch Principles](https://wiki.archlinux.org/index.php/Arch_Linux#Principles) as well as a library to manage services, packages and other Arch Linux aspects.
|
||||
|
||||
The guided installer will provide user friendly options along the way, but the keyword here is options, they are optional and will never be forced upon anyone. The guided installer itself is also optional to use if so desired and not forced upon anyone.
|
||||
|
||||
---
|
||||
|
||||
Archinstall has one fundamental function which is to be a flexible library to manage services, packages and other aspects inside the installed system. This library is in turn used by the provided guided installer but is also for anyone who wants to script their own installations.
|
||||
|
||||
Therefore, Archinstall will try its best to not introduce any breaking changes except for major releases which may break backwards compability after notifying about such changes.
|
||||
|
||||
# Scripting your own installation
|
||||
|
||||
You could just copy [guided.py](examples/guided.py) as a starting point.
|
||||
|
|
@ -35,23 +47,44 @@ import archinstall, getpass
|
|||
harddrive = archinstall.select_disk(archinstall.all_disks())
|
||||
disk_password = getpass.getpass(prompt='Disk password (won\'t echo): ')
|
||||
|
||||
with archinstall.Filesystem(harddrive, archinstall.GPT) as fs:
|
||||
# use_entire_disk() is a helper to not have to format manually
|
||||
fs.use_entire_disk('luks2')
|
||||
# We disable safety precautions in the library that protects the partitions
|
||||
harddrive.keep_partitions = False
|
||||
|
||||
harddrive.partition[0].format('fat32')
|
||||
with archinstall.luks2(harddrive.partition[1], 'luksloop', disk_password) as unlocked_device:
|
||||
unlocked_device.format('btrfs')
|
||||
# First, we configure the basic filesystem layout
|
||||
with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs:
|
||||
# We create a filesystem layout that will use the entire drive
|
||||
# (this is a helper function, you can partition manually as well)
|
||||
fs.use_entire_disk(root_filesystem_type='btrfs')
|
||||
|
||||
with archinstall.Installer(unlocked_device, hostname='testmachine') as installation:
|
||||
if installation.minimal_installation():
|
||||
installation.add_bootloader(harddrive.partition[0])
|
||||
boot = fs.find_partition('/boot')
|
||||
root = fs.find_partition('/')
|
||||
|
||||
installation.add_additional_packages(['nano', 'wget', 'git'])
|
||||
installation.install_profile('awesome')
|
||||
boot.format('vfat')
|
||||
|
||||
installation.user_create('anton', 'test')
|
||||
installation.user_set_pw('root', 'toor')
|
||||
# Set the flat for encrypted to allow for encryption and then encrypt
|
||||
root.encrypted = True
|
||||
root.encrypt(password=archinstall.arguments.get('!encryption-password', None))
|
||||
|
||||
with archinstall.luks2(root, 'luksloop', disk_password) as unlocked_root:
|
||||
unlocked_root.format(root.filesystem)
|
||||
unlocked_root.mount('/mnt')
|
||||
|
||||
boot.mount('/mnt/boot')
|
||||
|
||||
with archinstall.Installer('/mnt') as installation:
|
||||
if installation.minimal_installation():
|
||||
installation.set_hostname('minimal-arch')
|
||||
installation.add_bootloader()
|
||||
|
||||
installation.add_additional_packages(['nano', 'wget', 'git'])
|
||||
|
||||
# Optionally, install a profile of choice.
|
||||
# In this case, we install a minimal profile that is empty
|
||||
installation.install_profile('minimal')
|
||||
|
||||
installation.user_create('devel', 'devel')
|
||||
installation.user_set_pw('root', 'airoot')
|
||||
|
||||
```
|
||||
|
||||
This installer will perform the following:
|
||||
|
|
|
|||
|
|
@ -126,6 +126,18 @@ class BlockDevice():
|
|||
def partition_table_type(self):
|
||||
return GPT
|
||||
|
||||
@property
|
||||
def uuid(self):
|
||||
log(f'BlockDevice().uuid is untested!', level=LOG_LEVELS.Warning, fg='yellow')
|
||||
"""
|
||||
Returns the disk UUID 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+UUID {self.path}'))
|
||||
for partition in json.loads(lsblk.decode('UTF-8'))['blockdevices']:
|
||||
return partition.get('uuid', None)
|
||||
|
||||
def has_partitions(self):
|
||||
return len(self.partitions)
|
||||
|
||||
|
|
@ -166,7 +178,7 @@ class Partition():
|
|||
self.mountpoint = target
|
||||
|
||||
if not self.filesystem and autodetect_filesystem:
|
||||
if (fstype := mount_information.get('fstype', get_filesystem_type(self.real_device))):
|
||||
if (fstype := mount_information.get('fstype', get_filesystem_type(path))):
|
||||
self.filesystem = fstype
|
||||
|
||||
if self.filesystem == 'crypto_LUKS':
|
||||
|
|
@ -214,14 +226,15 @@ class Partition():
|
|||
|
||||
self._encrypted = value
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self.real_device
|
||||
|
||||
@property
|
||||
def real_device(self):
|
||||
if not self._encrypted:
|
||||
return self.path
|
||||
else:
|
||||
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}"
|
||||
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}')
|
||||
return self.path
|
||||
|
||||
|
|
@ -366,14 +379,16 @@ class Partition():
|
|||
if not fs:
|
||||
if not self.filesystem: raise DiskError(f'Need to format (or define) the filesystem on {self} before mounting.')
|
||||
fs = self.filesystem
|
||||
## libc has some issues with loop devices, defaulting back to sys calls
|
||||
# ret = libc.mount(self.path.encode(), target.encode(), fs.encode(), 0, options.encode())
|
||||
# if ret < 0:
|
||||
# errno = ctypes.get_errno()
|
||||
# raise OSError(errno, f"Error mounting {self.path} ({fs}) on {target} with options '{options}': {os.strerror(errno)}")
|
||||
if sys_command(f'/usr/bin/mount {self.path} {target}').exit_code == 0:
|
||||
self.mountpoint = target
|
||||
return True
|
||||
|
||||
pathlib.Path(target).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
try:
|
||||
sys_command(f'/usr/bin/mount {self.path} {target}')
|
||||
except SysCallError as err:
|
||||
raise err
|
||||
|
||||
self.mountpoint = target
|
||||
return True
|
||||
|
||||
def unmount(self):
|
||||
try:
|
||||
|
|
@ -572,6 +587,24 @@ def get_mount_info(path):
|
|||
|
||||
return output['filesystems'][0]
|
||||
|
||||
def get_partitions_in_use(mountpoint):
|
||||
try:
|
||||
output = b''.join(sys_command(f'/usr/bin/findmnt --json -R {mountpoint}'))
|
||||
except SysCallError:
|
||||
return {}
|
||||
|
||||
mounts = []
|
||||
|
||||
output = output.decode('UTF-8')
|
||||
output = json.loads(output)
|
||||
for target in output.get('filesystems', []):
|
||||
mounts.append(Partition(target['source'], None, filesystem=target.get('fstype', None), mountpoint=target['target']))
|
||||
|
||||
for child in target.get('children', []):
|
||||
mounts.append(Partition(child['source'], None, filesystem=child.get('fstype', None), mountpoint=child['target']))
|
||||
|
||||
return mounts
|
||||
|
||||
def get_filesystem_type(path):
|
||||
try:
|
||||
handle = sys_command(f"blkid -o value -s TYPE {path}")
|
||||
|
|
|
|||
|
|
@ -34,30 +34,21 @@ class Installer():
|
|||
:type hostname: str, optional
|
||||
|
||||
"""
|
||||
def __init__(self, partition, boot_partition, *, base_packages='base base-devel linux linux-firmware efibootmgr', profile=None, mountpoint='/mnt', hostname='ArchInstalled', logdir=None, logfile=None):
|
||||
self.profile = profile
|
||||
self.hostname = hostname
|
||||
self.mountpoint = mountpoint
|
||||
def __init__(self, target, *, base_packages='base base-devel linux linux-firmware efibootmgr'):
|
||||
self.target = target
|
||||
self.init_time = time.strftime('%Y-%m-%d_%H-%M-%S')
|
||||
self.milliseconds = int(str(time.time()).split('.')[1])
|
||||
|
||||
if logdir:
|
||||
storage['LOG_PATH'] = logdir
|
||||
if logfile:
|
||||
storage['LOG_FILE'] = logfile
|
||||
|
||||
self.helper_flags = {
|
||||
'bootloader' : False,
|
||||
'base' : False,
|
||||
'user' : False # Root counts as a user, if additional users are skipped.
|
||||
'bootloader' : False
|
||||
}
|
||||
|
||||
self.base_packages = base_packages.split(' ') if type(base_packages) is str else base_packages
|
||||
self.post_base_install = []
|
||||
storage['session'] = self
|
||||
|
||||
self.partition = partition
|
||||
self.boot_partition = boot_partition
|
||||
storage['session'] = self
|
||||
self.partitions = get_partitions_in_use(self.target)
|
||||
|
||||
def log(self, *args, level=LOG_LEVELS.Debug, **kwargs):
|
||||
"""
|
||||
|
|
@ -67,9 +58,6 @@ class Installer():
|
|||
log(*args, level=level, **kwargs)
|
||||
|
||||
def __enter__(self, *args, **kwargs):
|
||||
self.partition.mount(self.mountpoint)
|
||||
os.makedirs(f'{self.mountpoint}/boot', exist_ok=True)
|
||||
self.boot_partition.mount(f'{self.mountpoint}/boot')
|
||||
return self
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
|
|
@ -112,18 +100,18 @@ class Installer():
|
|||
if (filename := storage.get('LOG_FILE', None)):
|
||||
absolute_logfile = os.path.join(storage.get('LOG_PATH', './'), filename)
|
||||
|
||||
if not os.path.isdir(f"{self.mountpoint}/{os.path.dirname(absolute_logfile)}"):
|
||||
os.makedirs(f"{self.mountpoint}/{os.path.dirname(absolute_logfile)}")
|
||||
if not os.path.isdir(f"{self.target}/{os.path.dirname(absolute_logfile)}"):
|
||||
os.makedirs(f"{self.target}/{os.path.dirname(absolute_logfile)}")
|
||||
|
||||
shutil.copy2(absolute_logfile, f"{self.mountpoint}/{absolute_logfile}")
|
||||
shutil.copy2(absolute_logfile, f"{self.target}/{absolute_logfile}")
|
||||
|
||||
return True
|
||||
|
||||
def mount(self, partition, mountpoint, create_mountpoint=True):
|
||||
if create_mountpoint and not os.path.isdir(f'{self.mountpoint}{mountpoint}'):
|
||||
os.makedirs(f'{self.mountpoint}{mountpoint}')
|
||||
if create_mountpoint and not os.path.isdir(f'{self.target}{mountpoint}'):
|
||||
os.makedirs(f'{self.target}{mountpoint}')
|
||||
|
||||
partition.mount(f'{self.mountpoint}{mountpoint}')
|
||||
partition.mount(f'{self.target}{mountpoint}')
|
||||
|
||||
def post_install_check(self, *args, **kwargs):
|
||||
return [step for step, flag in self.helper_flags.items() if flag is False]
|
||||
|
|
@ -133,7 +121,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.target} {" ".join(packages)}', **kwargs)).exit_code == 0:
|
||||
return True
|
||||
else:
|
||||
self.log(f'Could not strap in packages: {pacstrap.exit_code}', level=LOG_LEVELS.Info)
|
||||
|
|
@ -141,42 +129,41 @@ class Installer():
|
|||
self.log(f'Could not sync mirrors: {sync_mirrors.exit_code}', level=LOG_LEVELS.Info)
|
||||
|
||||
def set_mirrors(self, mirrors):
|
||||
return use_mirrors(mirrors, destination=f'{self.mountpoint}/etc/pacman.d/mirrorlist')
|
||||
return use_mirrors(mirrors, destination=f'{self.target}/etc/pacman.d/mirrorlist')
|
||||
|
||||
def genfstab(self, flags='-pU'):
|
||||
self.log(f"Updating {self.mountpoint}/etc/fstab", level=LOG_LEVELS.Info)
|
||||
self.log(f"Updating {self.target}/etc/fstab", level=LOG_LEVELS.Info)
|
||||
|
||||
fstab = sys_command(f'/usr/bin/genfstab {flags} {self.mountpoint}').trace_log
|
||||
with open(f"{self.mountpoint}/etc/fstab", 'ab') as fstab_fh:
|
||||
fstab = sys_command(f'/usr/bin/genfstab {flags} {self.target}').trace_log
|
||||
with open(f"{self.target}/etc/fstab", 'ab') as fstab_fh:
|
||||
fstab_fh.write(fstab)
|
||||
|
||||
if not os.path.isfile(f'{self.mountpoint}/etc/fstab'):
|
||||
if not os.path.isfile(f'{self.target}/etc/fstab'):
|
||||
raise RequirementError(f'Could not generate fstab, strapping in packages most likely failed (disk out of space?)\n{fstab}')
|
||||
|
||||
return True
|
||||
|
||||
def set_hostname(self, hostname=None, *args, **kwargs):
|
||||
if not hostname: hostname = self.hostname
|
||||
with open(f'{self.mountpoint}/etc/hostname', 'w') as fh:
|
||||
fh.write(self.hostname + '\n')
|
||||
def set_hostname(self, hostname :str, *args, **kwargs):
|
||||
with open(f'{self.target}/etc/hostname', 'w') as fh:
|
||||
fh.write(hostname + '\n')
|
||||
|
||||
def set_locale(self, locale, encoding='UTF-8', *args, **kwargs):
|
||||
if not len(locale): return True
|
||||
|
||||
with open(f'{self.mountpoint}/etc/locale.gen', 'a') as fh:
|
||||
with open(f'{self.target}/etc/locale.gen', 'a') as fh:
|
||||
fh.write(f'{locale}.{encoding} {encoding}\n')
|
||||
with open(f'{self.mountpoint}/etc/locale.conf', 'w') as fh:
|
||||
with open(f'{self.target}/etc/locale.conf', 'w') as fh:
|
||||
fh.write(f'LANG={locale}.{encoding}\n')
|
||||
|
||||
return True if sys_command(f'/usr/bin/arch-chroot {self.mountpoint} locale-gen').exit_code == 0 else False
|
||||
return True if sys_command(f'/usr/bin/arch-chroot {self.target} locale-gen').exit_code == 0 else False
|
||||
|
||||
def set_timezone(self, zone, *args, **kwargs):
|
||||
if not zone: return True
|
||||
if not len(zone): return True # Redundant
|
||||
|
||||
if (pathlib.Path("/usr")/"share"/"zoneinfo"/zone).exists():
|
||||
(pathlib.Path(self.mountpoint)/"etc"/"localtime").unlink(missing_ok=True)
|
||||
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} ln -s /usr/share/zoneinfo/{zone} /etc/localtime')
|
||||
(pathlib.Path(self.target)/"etc"/"localtime").unlink(missing_ok=True)
|
||||
sys_command(f'/usr/bin/arch-chroot {self.target} ln -s /usr/share/zoneinfo/{zone} /etc/localtime')
|
||||
return True
|
||||
else:
|
||||
self.log(
|
||||
|
|
@ -196,7 +183,7 @@ class Installer():
|
|||
return self.arch_chroot(f'systemctl enable {service}').exit_code == 0
|
||||
|
||||
def run_command(self, cmd, *args, **kwargs):
|
||||
return sys_command(f'/usr/bin/arch-chroot {self.mountpoint} {cmd}')
|
||||
return sys_command(f'/usr/bin/arch-chroot {self.target} {cmd}')
|
||||
|
||||
def arch_chroot(self, cmd, *args, **kwargs):
|
||||
return self.run_command(cmd)
|
||||
|
|
@ -216,15 +203,15 @@ class Installer():
|
|||
|
||||
conf = Networkd(Match={"Name": nic}, Network=network)
|
||||
|
||||
with open(f"{self.mountpoint}/etc/systemd/network/10-{nic}.network", "a") as netconf:
|
||||
with open(f"{self.target}/etc/systemd/network/10-{nic}.network", "a") as netconf:
|
||||
netconf.write(str(conf))
|
||||
|
||||
def copy_ISO_network_config(self, enable_services=False):
|
||||
# Copy (if any) iwd password and config files
|
||||
if os.path.isdir('/var/lib/iwd/'):
|
||||
if (psk_files := glob.glob('/var/lib/iwd/*.psk')):
|
||||
if not os.path.isdir(f"{self.mountpoint}/var/lib/iwd"):
|
||||
os.makedirs(f"{self.mountpoint}/var/lib/iwd")
|
||||
if not os.path.isdir(f"{self.target}/var/lib/iwd"):
|
||||
os.makedirs(f"{self.target}/var/lib/iwd")
|
||||
|
||||
if enable_services:
|
||||
# If we haven't installed the base yet (function called pre-maturely)
|
||||
|
|
@ -244,15 +231,15 @@ class Installer():
|
|||
self.enable_service('iwd')
|
||||
|
||||
for psk in psk_files:
|
||||
shutil.copy2(psk, f"{self.mountpoint}/var/lib/iwd/{os.path.basename(psk)}")
|
||||
shutil.copy2(psk, f"{self.target}/var/lib/iwd/{os.path.basename(psk)}")
|
||||
|
||||
# Copy (if any) systemd-networkd config files
|
||||
if (netconfigurations := glob.glob('/etc/systemd/network/*')):
|
||||
if not os.path.isdir(f"{self.mountpoint}/etc/systemd/network/"):
|
||||
os.makedirs(f"{self.mountpoint}/etc/systemd/network/")
|
||||
if not os.path.isdir(f"{self.target}/etc/systemd/network/"):
|
||||
os.makedirs(f"{self.target}/etc/systemd/network/")
|
||||
|
||||
for netconf_file in netconfigurations:
|
||||
shutil.copy2(netconf_file, f"{self.mountpoint}/etc/systemd/network/{os.path.basename(netconf_file)}")
|
||||
shutil.copy2(netconf_file, f"{self.target}/etc/systemd/network/{os.path.basename(netconf_file)}")
|
||||
|
||||
if enable_services:
|
||||
# If we haven't installed the base yet (function called pre-maturely)
|
||||
|
|
@ -269,54 +256,69 @@ class Installer():
|
|||
|
||||
return True
|
||||
|
||||
def detect_encryption(self, partition):
|
||||
if partition.encrypted:
|
||||
return partition
|
||||
elif partition.parent not in partition.path and Partition(partition.parent, None, autodetect_filesystem=True).filesystem == 'crypto_LUKS':
|
||||
return Partition(partition.parent, None, autodetect_filesystem=True)
|
||||
|
||||
return False
|
||||
|
||||
def minimal_installation(self):
|
||||
## Add necessary packages if encrypting the drive
|
||||
## (encrypted partitions default to btrfs for now, so we need btrfs-progs)
|
||||
## TODO: Perhaps this should be living in the function which dictates
|
||||
## the partitioning. Leaving here for now.
|
||||
if self.partition.filesystem == 'btrfs':
|
||||
#if self.partition.encrypted:
|
||||
self.base_packages.append('btrfs-progs')
|
||||
if self.partition.filesystem == 'xfs':
|
||||
self.base_packages.append('xfsprogs')
|
||||
if self.partition.filesystem == 'f2fs':
|
||||
self.base_packages.append('f2fs-tools')
|
||||
MODULES = []
|
||||
BINARIES = []
|
||||
FILES = []
|
||||
HOOKS = ["base", "udev", "autodetect", "keyboard", "keymap", "modconf", "block", "filesystems", "fsck"]
|
||||
|
||||
for partition in self.partitions:
|
||||
if partition.filesystem == 'btrfs':
|
||||
#if partition.encrypted:
|
||||
self.base_packages.append('btrfs-progs')
|
||||
if partition.filesystem == 'xfs':
|
||||
self.base_packages.append('xfsprogs')
|
||||
if partition.filesystem == 'f2fs':
|
||||
self.base_packages.append('f2fs-tools')
|
||||
|
||||
# Configure mkinitcpio to handle some specific use cases.
|
||||
if partition.filesystem == 'btrfs':
|
||||
if 'btrfs' not in MODULES:
|
||||
MODULES.append('btrfs')
|
||||
if '/usr/bin/btrfs-progs' not in BINARIES:
|
||||
BINARIES.append('/usr/bin/btrfs')
|
||||
|
||||
if self.detect_encryption(partition):
|
||||
if 'encrypt' not in HOOKS:
|
||||
HOOKS.insert(HOOKS.index('filesystems'), 'encrypt')
|
||||
|
||||
self.pacstrap(self.base_packages)
|
||||
self.helper_flags['base-strapped'] = True
|
||||
#self.genfstab()
|
||||
|
||||
with open(f"{self.mountpoint}/etc/fstab", "a") as fstab:
|
||||
with open(f"{self.target}/etc/fstab", "a") as fstab:
|
||||
fstab.write(
|
||||
"\ntmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0\n"
|
||||
) # Redundant \n at the start? who knows?
|
||||
|
||||
## TODO: Support locale and timezone
|
||||
#os.remove(f'{self.mountpoint}/etc/localtime')
|
||||
#sys_command(f'/usr/bin/arch-chroot {self.mountpoint} ln -s /usr/share/zoneinfo/{localtime} /etc/localtime')
|
||||
#os.remove(f'{self.target}/etc/localtime')
|
||||
#sys_command(f'/usr/bin/arch-chroot {self.target} ln -s /usr/share/zoneinfo/{localtime} /etc/localtime')
|
||||
#sys_command('/usr/bin/arch-chroot /mnt hwclock --hctosys --localtime')
|
||||
self.set_hostname()
|
||||
self.set_hostname('archinstall')
|
||||
self.set_locale('en_US')
|
||||
|
||||
# TODO: Use python functions for this
|
||||
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} chmod 700 /root')
|
||||
sys_command(f'/usr/bin/arch-chroot {self.target} chmod 700 /root')
|
||||
|
||||
# Configure mkinitcpio to handle some specific use cases.
|
||||
# TODO: Yes, we should not overwrite the entire thing, but for now this should be fine
|
||||
# since we just installed the base system.
|
||||
if self.partition.filesystem == 'btrfs':
|
||||
with open(f'{self.mountpoint}/etc/mkinitcpio.conf', 'w') as mkinit:
|
||||
mkinit.write('MODULES=(btrfs)\n')
|
||||
mkinit.write('BINARIES=(/usr/bin/btrfs)\n')
|
||||
mkinit.write('FILES=()\n')
|
||||
mkinit.write('HOOKS=(base udev autodetect keyboard keymap modconf block encrypt filesystems fsck)\n')
|
||||
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} mkinitcpio -p linux')
|
||||
elif self.partition.encrypted:
|
||||
with open(f'{self.mountpoint}/etc/mkinitcpio.conf', 'w') as mkinit:
|
||||
mkinit.write('MODULES=()\n')
|
||||
mkinit.write('BINARIES=()\n')
|
||||
mkinit.write('FILES=()\n')
|
||||
mkinit.write('HOOKS=(base udev autodetect keyboard keymap modconf block encrypt filesystems fsck)\n')
|
||||
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} mkinitcpio -p linux')
|
||||
with open(f'{self.target}/etc/mkinitcpio.conf', 'w') as mkinit:
|
||||
mkinit.write(f"MODULES=({' '.join(MODULES)})\n")
|
||||
mkinit.write(f"BINARIES=({' '.join(BINARIES)})\n")
|
||||
mkinit.write(f"FILES=({' '.join(FILES)})\n")
|
||||
mkinit.write(f"HOOKS=({' '.join(HOOKS)})\n")
|
||||
sys_command(f'/usr/bin/arch-chroot {self.target} mkinitcpio -p linux')
|
||||
|
||||
self.helper_flags['base'] = True
|
||||
|
||||
|
|
@ -328,7 +330,15 @@ class Installer():
|
|||
return True
|
||||
|
||||
def add_bootloader(self, bootloader='systemd-bootctl'):
|
||||
self.log(f'Adding bootloader {bootloader} to {self.boot_partition}', level=LOG_LEVELS.Info)
|
||||
boot_partition = None
|
||||
root_partition = None
|
||||
for partition in self.partitions:
|
||||
if partition.mountpoint == self.target+'/boot':
|
||||
boot_partition = partition
|
||||
elif partition.mountpoint == self.target:
|
||||
root_partition = partition
|
||||
|
||||
self.log(f'Adding bootloader {bootloader} to {boot_partition}', level=LOG_LEVELS.Info)
|
||||
|
||||
if bootloader == 'systemd-bootctl':
|
||||
# TODO: Ideally we would want to check if another config
|
||||
|
|
@ -336,11 +346,11 @@ class Installer():
|
|||
# And in which case we should do some clean up.
|
||||
|
||||
# Install the boot loader
|
||||
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} bootctl --no-variables --path=/boot install')
|
||||
sys_command(f'/usr/bin/arch-chroot {self.target} bootctl --no-variables --path=/boot install')
|
||||
|
||||
# Modify or create a loader.conf
|
||||
if os.path.isfile(f'{self.mountpoint}/boot/loader/loader.conf'):
|
||||
with open(f'{self.mountpoint}/boot/loader/loader.conf', 'r') as loader:
|
||||
if os.path.isfile(f'{self.target}/boot/loader/loader.conf'):
|
||||
with open(f'{self.target}/boot/loader/loader.conf', 'r') as loader:
|
||||
loader_data = loader.read().split('\n')
|
||||
else:
|
||||
loader_data = [
|
||||
|
|
@ -348,7 +358,7 @@ class Installer():
|
|||
f"timeout 5"
|
||||
]
|
||||
|
||||
with open(f'{self.mountpoint}/boot/loader/loader.conf', 'w') as loader:
|
||||
with open(f'{self.target}/boot/loader/loader.conf', 'w') as loader:
|
||||
for line in loader_data:
|
||||
if line[:8] == 'default ':
|
||||
loader.write(f'default {self.init_time}\n')
|
||||
|
|
@ -360,7 +370,7 @@ class Installer():
|
|||
#UUID = sys_command('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip()
|
||||
|
||||
# Setup the loader entry
|
||||
with open(f'{self.mountpoint}/boot/loader/entries/{self.init_time}.conf', 'w') as entry:
|
||||
with open(f'{self.target}/boot/loader/entries/{self.init_time}.conf', 'w') as entry:
|
||||
entry.write(f'# Created by: archinstall\n')
|
||||
entry.write(f'# Created on: {self.init_time}\n')
|
||||
entry.write(f'title Arch Linux\n')
|
||||
|
|
@ -370,28 +380,19 @@ class Installer():
|
|||
## so we'll use the old manual method until we get that sorted out.
|
||||
|
||||
|
||||
if self.partition.encrypted:
|
||||
log(f"Identifying root partition by DISK-UUID on {self.partition}, looking for '{os.path.basename(self.partition.real_device)}'.", level=LOG_LEVELS.Debug)
|
||||
for root, folders, uids in os.walk('/dev/disk/by-uuid'):
|
||||
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.real_device)}: {os.path.basename(real_path) == os.path.basename(self.partition.real_device)}", level=LOG_LEVELS.Debug)
|
||||
if not os.path.basename(real_path) == os.path.basename(self.partition.real_device): continue
|
||||
|
||||
entry.write(f'options cryptdevice=UUID={uid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n')
|
||||
|
||||
self.helper_flags['bootloader'] = bootloader
|
||||
return True
|
||||
break
|
||||
if (real_device := self.detect_encryption(root_partition)):
|
||||
# TODO: We need to detect if the encrypted device is a whole disk encryption,
|
||||
# or simply a partition encryption. Right now we assume it's a partition (and we always have)
|
||||
log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.uuid}'.", level=LOG_LEVELS.Debug)
|
||||
entry.write(f'options cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n')
|
||||
else:
|
||||
log(f"Identifying root partition by PART-UUID on {self.partition}, looking for '{os.path.basename(self.partition.path)}'.", level=LOG_LEVELS.Debug)
|
||||
entry.write(f'options root=PARTUUID={self.partition.uuid} rw intel_pstate=no_hwp\n')
|
||||
log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.uuid}'.", level=LOG_LEVELS.Debug)
|
||||
entry.write(f'options root=PARTUUID={root_partition.uuid} rw intel_pstate=no_hwp\n')
|
||||
|
||||
self.helper_flags['bootloader'] = bootloader
|
||||
return True
|
||||
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.")
|
||||
raise RequirementError(f"Could not identify the UUID of {root_partition}, there for {self.target}/boot/loader/entries/arch.conf will be broken until fixed.")
|
||||
else:
|
||||
raise RequirementError(f"Unknown (or not yet implemented) bootloader added to add_bootloader(): {bootloader}")
|
||||
|
||||
|
|
@ -416,19 +417,19 @@ class Installer():
|
|||
|
||||
def enable_sudo(self, entity :str, group=False):
|
||||
self.log(f'Enabling sudo permissions for {entity}.', level=LOG_LEVELS.Info)
|
||||
with open(f'{self.mountpoint}/etc/sudoers', 'a') as sudoers:
|
||||
with open(f'{self.target}/etc/sudoers', 'a') as sudoers:
|
||||
sudoers.write(f'{"%" if group else ""}{entity} ALL=(ALL) ALL\n')
|
||||
return True
|
||||
|
||||
def user_create(self, user :str, password=None, groups=[], sudo=False):
|
||||
self.log(f'Creating user {user}', level=LOG_LEVELS.Info)
|
||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} useradd -m -G wheel {user}'))
|
||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} useradd -m -G wheel {user}'))
|
||||
if password:
|
||||
self.user_set_pw(user, password)
|
||||
|
||||
if groups:
|
||||
for group in groups:
|
||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} gpasswd -a {user} {group}'))
|
||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} gpasswd -a {user} {group}'))
|
||||
|
||||
if sudo and self.enable_sudo(user):
|
||||
self.helper_flags['user'] = True
|
||||
|
|
@ -440,12 +441,12 @@ class Installer():
|
|||
# This means the root account isn't locked/disabled with * in /etc/passwd
|
||||
self.helper_flags['user'] = True
|
||||
|
||||
o = b''.join(sys_command(f"/usr/bin/arch-chroot {self.mountpoint} sh -c \"echo '{user}:{password}' | chpasswd\""))
|
||||
o = b''.join(sys_command(f"/usr/bin/arch-chroot {self.target} sh -c \"echo '{user}:{password}' | chpasswd\""))
|
||||
pass
|
||||
|
||||
def set_keyboard_language(self, language):
|
||||
if len(language.strip()):
|
||||
with open(f'{self.mountpoint}/etc/vconsole.conf', 'w') as vconsole:
|
||||
with open(f'{self.target}/etc/vconsole.conf', 'w') as vconsole:
|
||||
vconsole.write(f'KEYMAP={language}\n')
|
||||
vconsole.write(f'FONT=lat9w-16\n')
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -255,8 +255,10 @@ def select_disk(dict_o_disks):
|
|||
if len(drives) >= 1:
|
||||
for index, drive in enumerate(drives):
|
||||
print(f"{index}: {drive} ({dict_o_disks[drive]['size'], dict_o_disks[drive].device, dict_o_disks[drive]['label']})")
|
||||
drive = input('Select one of the above disks (by number or full path): ')
|
||||
if drive.isdigit():
|
||||
drive = input('Select one of the above disks (by number or full path) or write /mnt to skip partitioning: ')
|
||||
if drive.strip() == '/mnt':
|
||||
return None
|
||||
elif drive.isdigit():
|
||||
drive = int(drive)
|
||||
if drive >= len(drives):
|
||||
raise DiskError(f'Selected option "{drive}" is out of range')
|
||||
|
|
|
|||
|
|
@ -30,12 +30,14 @@ def ask_user_questions():
|
|||
archinstall.arguments['harddrive'] = archinstall.BlockDevice(archinstall.arguments['harddrive'])
|
||||
else:
|
||||
archinstall.arguments['harddrive'] = archinstall.select_disk(archinstall.all_disks())
|
||||
if archinstall.arguments['harddrive'] is None:
|
||||
archinstall.arguments['target-mount'] = '/mnt'
|
||||
|
||||
# Perform a quick sanity check on the selected harddrive.
|
||||
# 1. Check if it has partitions
|
||||
# 3. Check that we support the current partitions
|
||||
# 2. If so, ask if we should keep them or wipe everything
|
||||
if archinstall.arguments['harddrive'].has_partitions():
|
||||
if archinstall.arguments['harddrive'] and archinstall.arguments['harddrive'].has_partitions():
|
||||
archinstall.log(f"{archinstall.arguments['harddrive']} contains the following partitions:", fg='yellow')
|
||||
|
||||
# We curate a list pf supported partitions
|
||||
|
|
@ -114,14 +116,14 @@ def ask_user_questions():
|
|||
elif option == 'format-all':
|
||||
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
||||
archinstall.arguments['harddrive'].keep_partitions = False
|
||||
else:
|
||||
elif archinstall.arguments['harddrive']:
|
||||
# If the drive doesn't have any partitions, safely mark the disk with keep_partitions = False
|
||||
# and ask the user for a root filesystem.
|
||||
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
||||
archinstall.arguments['harddrive'].keep_partitions = False
|
||||
|
||||
# Get disk encryption password (or skip if blank)
|
||||
if not archinstall.arguments.get('!encryption-password', None):
|
||||
if archinstall.arguments['harddrive'] and archinstall.arguments.get('!encryption-password', None) is None:
|
||||
if (passwd := archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ')):
|
||||
archinstall.arguments['!encryption-password'] = passwd
|
||||
archinstall.arguments['harddrive'].encryption_password = archinstall.arguments['!encryption-password']
|
||||
|
|
@ -210,62 +212,63 @@ def perform_installation_steps():
|
|||
We mention the drive one last time, and count from 5 to 0.
|
||||
"""
|
||||
|
||||
print(f" ! Formatting {archinstall.arguments['harddrive']} in ", end='')
|
||||
archinstall.do_countdown()
|
||||
if archinstall.arguments.get('harddrive', None):
|
||||
print(f" ! Formatting {archinstall.arguments['harddrive']} in ", end='')
|
||||
archinstall.do_countdown()
|
||||
|
||||
"""
|
||||
Setup the blockdevice, filesystem (and optionally encryption).
|
||||
Once that's done, we'll hand over to perform_installation()
|
||||
"""
|
||||
with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs:
|
||||
# Wipe the entire drive if the disk flag `keep_partitions`is False.
|
||||
if archinstall.arguments['harddrive'].keep_partitions is False:
|
||||
fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'))
|
||||
|
||||
# Check if encryption is desired and mark the root partition as encrypted.
|
||||
if archinstall.arguments.get('!encryption-password', None):
|
||||
root_partition = fs.find_partition('/')
|
||||
root_partition.encrypted = True
|
||||
|
||||
# After the disk is ready, iterate the partitions and check
|
||||
# which ones are safe to format, and format those.
|
||||
for partition in archinstall.arguments['harddrive']:
|
||||
if partition.safe_to_format():
|
||||
# Partition might be marked as encrypted due to the filesystem type crypt_LUKS
|
||||
# But we might have omitted the encryption password question to skip encryption.
|
||||
# In which case partition.encrypted will be true, but passwd will be false.
|
||||
if partition.encrypted and (passwd := archinstall.arguments.get('!encryption-password', None)):
|
||||
partition.encrypt(password=passwd)
|
||||
"""
|
||||
Setup the blockdevice, filesystem (and optionally encryption).
|
||||
Once that's done, we'll hand over to perform_installation()
|
||||
"""
|
||||
with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs:
|
||||
# Wipe the entire drive if the disk flag `keep_partitions`is False.
|
||||
if archinstall.arguments['harddrive'].keep_partitions is False:
|
||||
fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'))
|
||||
|
||||
# Check if encryption is desired and mark the root partition as encrypted.
|
||||
if archinstall.arguments.get('!encryption-password', None):
|
||||
root_partition = fs.find_partition('/')
|
||||
root_partition.encrypted = True
|
||||
|
||||
# After the disk is ready, iterate the partitions and check
|
||||
# which ones are safe to format, and format those.
|
||||
for partition in archinstall.arguments['harddrive']:
|
||||
if partition.safe_to_format():
|
||||
# Partition might be marked as encrypted due to the filesystem type crypt_LUKS
|
||||
# But we might have omitted the encryption password question to skip encryption.
|
||||
# In which case partition.encrypted will be true, but passwd will be false.
|
||||
if partition.encrypted and (passwd := archinstall.arguments.get('!encryption-password', None)):
|
||||
partition.encrypt(password=passwd)
|
||||
else:
|
||||
partition.format()
|
||||
else:
|
||||
partition.format()
|
||||
archinstall.log(f"Did not format {partition} because .safe_to_format() returned False or .allow_formatting was False.", level=archinstall.LOG_LEVELS.Debug)
|
||||
|
||||
fs.find_partition('/boot').format('vfat')
|
||||
|
||||
if archinstall.arguments.get('!encryption-password', None):
|
||||
# First encrypt and unlock, then format the desired partition inside the encrypted part.
|
||||
# archinstall.luks2() encrypts the partition when entering the with context manager, and
|
||||
# unlocks the drive so that it can be used as a normal block-device within archinstall.
|
||||
with archinstall.luks2(fs.find_partition('/'), 'luksloop', archinstall.arguments.get('!encryption-password', None)) as unlocked_device:
|
||||
unlocked_device.format(fs.find_partition('/').filesystem)
|
||||
unlocked_device.mount('/mnt')
|
||||
else:
|
||||
archinstall.log(f"Did not format {partition} because .safe_to_format() returned False or .allow_formatting was False.", level=archinstall.LOG_LEVELS.Debug)
|
||||
fs.find_partition('/').format(fs.find_partition('/').filesystem)
|
||||
fs.find_partition('/').mount('/mnt')
|
||||
|
||||
if archinstall.arguments.get('!encryption-password', None):
|
||||
# First encrypt and unlock, then format the desired partition inside the encrypted part.
|
||||
# archinstall.luks2() encrypts the partition when entering the with context manager, and
|
||||
# unlocks the drive so that it can be used as a normal block-device within archinstall.
|
||||
with archinstall.luks2(fs.find_partition('/'), 'luksloop', archinstall.arguments.get('!encryption-password', None)) as unlocked_device:
|
||||
unlocked_device.format(fs.find_partition('/').filesystem)
|
||||
|
||||
perform_installation(device=unlocked_device,
|
||||
boot_partition=fs.find_partition('/boot'),
|
||||
language=archinstall.arguments['keyboard-language'],
|
||||
mirrors=archinstall.arguments['mirror-region'])
|
||||
else:
|
||||
perform_installation(device=fs.find_partition('/'),
|
||||
boot_partition=fs.find_partition('/boot'),
|
||||
language=archinstall.arguments['keyboard-language'],
|
||||
mirrors=archinstall.arguments['mirror-region'])
|
||||
fs.find_partition('/boot').mount('/mnt/boot')
|
||||
|
||||
perform_installation('/mnt')
|
||||
|
||||
|
||||
def perform_installation(device, boot_partition, language, mirrors):
|
||||
def perform_installation(mountpoint):
|
||||
"""
|
||||
Performs the installation steps on a block device.
|
||||
Only requirement is that the block devices are
|
||||
formatted and setup prior to entering this function.
|
||||
"""
|
||||
with archinstall.Installer(device, boot_partition=boot_partition, hostname=archinstall.arguments.get('hostname', 'Archinstall')) as installation:
|
||||
with archinstall.Installer(mountpoint) as installation:
|
||||
## if len(mirrors):
|
||||
# Certain services might be running that affects the system during installation.
|
||||
# Currently, only one such service is "reflector.service" which updates /etc/pacman.d/mirrorlist
|
||||
|
|
@ -274,10 +277,11 @@ def perform_installation(device, boot_partition, language, mirrors):
|
|||
while 'dead' not in (status := archinstall.service_state('reflector')):
|
||||
time.sleep(1)
|
||||
|
||||
archinstall.use_mirrors(mirrors) # Set the mirrors for the live medium
|
||||
archinstall.use_mirrors(archinstall.arguments['mirror-region']) # Set the mirrors for the live medium
|
||||
if installation.minimal_installation():
|
||||
installation.set_mirrors(mirrors) # Set the mirrors in the installation medium
|
||||
installation.set_keyboard_language(language)
|
||||
installation.set_hostname(archinstall.arguments['hostname'])
|
||||
installation.set_mirrors(archinstall.arguments['mirror-region']) # Set the mirrors in the installation medium
|
||||
installation.set_keyboard_language(archinstall.arguments['keyboard-language'])
|
||||
installation.add_bootloader()
|
||||
|
||||
# If user selected to copy the current ISO network configuration
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import archinstall, getpass
|
||||
import archinstall
|
||||
|
||||
# Select a harddrive and a disk password
|
||||
archinstall.log(f"Minimal only supports:")
|
||||
|
|
@ -10,14 +10,14 @@ if archinstall.arguments.get('help', None):
|
|||
archinstall.log(f" - Optional systemd network via --network")
|
||||
|
||||
archinstall.arguments['harddrive'] = archinstall.select_disk(archinstall.all_disks())
|
||||
archinstall.arguments['harddrive'].keep_partitions = False
|
||||
|
||||
def install_on(root, boot):
|
||||
# We kick off the installer by telling it where the root and boot lives
|
||||
with archinstall.Installer(root, boot_partition=boot, hostname='minimal-arch') as installation:
|
||||
def install_on(mountpoint):
|
||||
# We kick off the installer by telling it where the
|
||||
with archinstall.Installer(mountpoint) as installation:
|
||||
# Strap in the base system, add a boot loader and configure
|
||||
# some other minor details as specified by this profile and user.
|
||||
if installation.minimal_installation():
|
||||
installation.set_hostname('minimal-arch')
|
||||
installation.add_bootloader()
|
||||
|
||||
# Optionally enable networking:
|
||||
|
|
@ -36,29 +36,36 @@ def install_on(root, boot):
|
|||
archinstall.log(f" * root (password: airoot)")
|
||||
archinstall.log(f" * devel (password: devel)")
|
||||
|
||||
print(f" ! Formatting {archinstall.arguments['harddrive']} in ", end='')
|
||||
archinstall.do_countdown()
|
||||
if archinstall.arguments['harddrive']:
|
||||
archinstall.arguments['harddrive'].keep_partitions = False
|
||||
|
||||
print(f" ! Formatting {archinstall.arguments['harddrive']} in ", end='')
|
||||
archinstall.do_countdown()
|
||||
|
||||
# First, we configure the basic filesystem layout
|
||||
with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs:
|
||||
# We use the entire disk instead of setting up partitions on your own
|
||||
if archinstall.arguments['harddrive'].keep_partitions is False:
|
||||
fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'))
|
||||
# First, we configure the basic filesystem layout
|
||||
with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs:
|
||||
# We use the entire disk instead of setting up partitions on your own
|
||||
if archinstall.arguments['harddrive'].keep_partitions is False:
|
||||
fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'))
|
||||
|
||||
boot = fs.find_partition('/boot')
|
||||
root = fs.find_partition('/')
|
||||
boot = fs.find_partition('/boot')
|
||||
root = fs.find_partition('/')
|
||||
|
||||
boot.format('vfat')
|
||||
boot.format('vfat')
|
||||
|
||||
# We encrypt the root partition if we got a password to do so with,
|
||||
# Otherwise we just skip straight to formatting and installation
|
||||
if archinstall.arguments.get('!encryption-password', None):
|
||||
root.encrypt()
|
||||
# We encrypt the root partition if we got a password to do so with,
|
||||
# Otherwise we just skip straight to formatting and installation
|
||||
if archinstall.arguments.get('!encryption-password', None):
|
||||
root.encrypted = True
|
||||
root.encrypt(password=archinstall.arguments.get('!encryption-password', None))
|
||||
|
||||
with archinstall.luks2(root, 'luksloop', archinstall.arguments.get('!encryption-password', None)) as unlocked_root:
|
||||
unlocked_root.format(root.filesystem)
|
||||
with archinstall.luks2(root, 'luksloop', archinstall.arguments.get('!encryption-password', None)) as unlocked_root:
|
||||
unlocked_root.format(root.filesystem)
|
||||
unlocked_root.mount('/mnt')
|
||||
else:
|
||||
root.format(root.filesystem)
|
||||
root.mount('/mnt')
|
||||
|
||||
install_on(unlocked_root)
|
||||
else:
|
||||
root.format(root.filesystem)
|
||||
install_on(root, boot)
|
||||
boot.mount('/mnt/boot')
|
||||
|
||||
install_on('/mnt')
|
||||
Loading…
Reference in New Issue