Merge branch 'master' into torxed-v2.2.0
This commit is contained in:
commit
398f95ee56
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
|
# 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
|
# Scripting your own installation
|
||||||
|
|
||||||
You could just copy [guided.py](examples/guided.py) as a starting point.
|
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())
|
harddrive = archinstall.select_disk(archinstall.all_disks())
|
||||||
disk_password = getpass.getpass(prompt='Disk password (won\'t echo): ')
|
disk_password = getpass.getpass(prompt='Disk password (won\'t echo): ')
|
||||||
|
|
||||||
with archinstall.Filesystem(harddrive, archinstall.GPT) as fs:
|
# We disable safety precautions in the library that protects the partitions
|
||||||
# use_entire_disk() is a helper to not have to format manually
|
harddrive.keep_partitions = False
|
||||||
fs.use_entire_disk('luks2')
|
|
||||||
|
|
||||||
harddrive.partition[0].format('fat32')
|
# First, we configure the basic filesystem layout
|
||||||
with archinstall.luks2(harddrive.partition[1], 'luksloop', disk_password) as unlocked_device:
|
with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs:
|
||||||
unlocked_device.format('btrfs')
|
# 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:
|
boot = fs.find_partition('/boot')
|
||||||
if installation.minimal_installation():
|
root = fs.find_partition('/')
|
||||||
installation.add_bootloader(harddrive.partition[0])
|
|
||||||
|
|
||||||
installation.add_additional_packages(['nano', 'wget', 'git'])
|
boot.format('vfat')
|
||||||
installation.install_profile('awesome')
|
|
||||||
|
|
||||||
installation.user_create('anton', 'test')
|
# Set the flat for encrypted to allow for encryption and then encrypt
|
||||||
installation.user_set_pw('root', 'toor')
|
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:
|
This installer will perform the following:
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,18 @@ class BlockDevice():
|
||||||
def partition_table_type(self):
|
def partition_table_type(self):
|
||||||
return GPT
|
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):
|
def has_partitions(self):
|
||||||
return len(self.partitions)
|
return len(self.partitions)
|
||||||
|
|
||||||
|
|
@ -167,7 +179,7 @@ class Partition():
|
||||||
self.mountpoint = target
|
self.mountpoint = target
|
||||||
|
|
||||||
if not self.filesystem and autodetect_filesystem:
|
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
|
self.filesystem = fstype
|
||||||
|
|
||||||
if self.filesystem == 'crypto_LUKS':
|
if self.filesystem == 'crypto_LUKS':
|
||||||
|
|
@ -188,9 +200,9 @@ class Partition():
|
||||||
mount_repr = f", rel_mountpoint={self.target_mountpoint}"
|
mount_repr = f", rel_mountpoint={self.target_mountpoint}"
|
||||||
|
|
||||||
if self._encrypted:
|
if self._encrypted:
|
||||||
return f'Partition(path={self.path}, real_device={self.real_device}, fs={self.filesystem}{mount_repr})'
|
return f'Partition(path={self.path}, size={self.size}, real_device={self.real_device}, fs={self.filesystem}{mount_repr})'
|
||||||
else:
|
else:
|
||||||
return f'Partition(path={self.path}, fs={self.filesystem}{mount_repr})'
|
return f'Partition(path={self.path}, size={self.size}, fs={self.filesystem}{mount_repr})'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def uuid(self) -> str:
|
def uuid(self) -> str:
|
||||||
|
|
@ -215,14 +227,15 @@ class Partition():
|
||||||
|
|
||||||
self._encrypted = value
|
self._encrypted = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent(self):
|
||||||
|
return self.real_device
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def real_device(self):
|
def real_device(self):
|
||||||
if not self._encrypted:
|
for blockdevice in json.loads(b''.join(sys_command('lsblk -J')).decode('UTF-8'))['blockdevices']:
|
||||||
return self.path
|
if (parent := self.find_parent_of(blockdevice, os.path.basename(self.path))):
|
||||||
else:
|
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}')
|
# raise DiskError(f'Could not find appropriate parent for encrypted partition {self}')
|
||||||
return self.path
|
return self.path
|
||||||
|
|
||||||
|
|
@ -367,14 +380,16 @@ class Partition():
|
||||||
if not fs:
|
if not fs:
|
||||||
if not self.filesystem: raise DiskError(f'Need to format (or define) the filesystem on {self} before mounting.')
|
if not self.filesystem: raise DiskError(f'Need to format (or define) the filesystem on {self} before mounting.')
|
||||||
fs = self.filesystem
|
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())
|
pathlib.Path(target).mkdir(parents=True, exist_ok=True)
|
||||||
# if ret < 0:
|
|
||||||
# errno = ctypes.get_errno()
|
try:
|
||||||
# raise OSError(errno, f"Error mounting {self.path} ({fs}) on {target} with options '{options}': {os.strerror(errno)}")
|
sys_command(f'/usr/bin/mount {self.path} {target}')
|
||||||
if sys_command(f'/usr/bin/mount {self.path} {target}').exit_code == 0:
|
except SysCallError as err:
|
||||||
self.mountpoint = target
|
raise err
|
||||||
return True
|
|
||||||
|
self.mountpoint = target
|
||||||
|
return True
|
||||||
|
|
||||||
def unmount(self):
|
def unmount(self):
|
||||||
try:
|
try:
|
||||||
|
|
@ -589,6 +604,24 @@ def get_mount_info(path):
|
||||||
|
|
||||||
return output['filesystems'][0]
|
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):
|
def get_filesystem_type(path):
|
||||||
try:
|
try:
|
||||||
handle = sys_command(f"blkid -o value -s TYPE {path}")
|
handle = sys_command(f"blkid -o value -s TYPE {path}")
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,6 @@ class HardwareIncompatibilityError(BaseException):
|
||||||
class PermissionError(BaseException):
|
class PermissionError(BaseException):
|
||||||
pass
|
pass
|
||||||
class UserError(BaseException):
|
class UserError(BaseException):
|
||||||
|
pass
|
||||||
|
class ServiceException(BaseException):
|
||||||
pass
|
pass
|
||||||
|
|
@ -39,30 +39,21 @@ class Installer():
|
||||||
:type hostname: str, optional
|
:type hostname: str, optional
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, partition, boot_partition, *, base_packages=__base_packages__, profile=None, mountpoint='/mnt', hostname='ArchInstalled', logdir=None, logfile=None):
|
def __init__(self, target, *, base_packages='base base-devel linux linux-firmware efibootmgr'):
|
||||||
self.profile = profile
|
self.target = target
|
||||||
self.hostname = hostname
|
|
||||||
self.mountpoint = mountpoint
|
|
||||||
self.init_time = time.strftime('%Y-%m-%d_%H-%M-%S')
|
self.init_time = time.strftime('%Y-%m-%d_%H-%M-%S')
|
||||||
self.milliseconds = int(str(time.time()).split('.')[1])
|
self.milliseconds = int(str(time.time()).split('.')[1])
|
||||||
|
|
||||||
if logdir:
|
|
||||||
storage['LOG_PATH'] = logdir
|
|
||||||
if logfile:
|
|
||||||
storage['LOG_FILE'] = logfile
|
|
||||||
|
|
||||||
self.helper_flags = {
|
self.helper_flags = {
|
||||||
'bootloader' : False,
|
|
||||||
'base' : 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.base_packages = base_packages.split(' ') if type(base_packages) is str else base_packages
|
||||||
self.post_base_install = []
|
self.post_base_install = []
|
||||||
storage['session'] = self
|
|
||||||
|
|
||||||
self.partition = partition
|
storage['session'] = self
|
||||||
self.boot_partition = boot_partition
|
self.partitions = get_partitions_in_use(self.target)
|
||||||
|
|
||||||
def log(self, *args, level=LOG_LEVELS.Debug, **kwargs):
|
def log(self, *args, level=LOG_LEVELS.Debug, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -72,11 +63,6 @@ class Installer():
|
||||||
log(*args, level=level, **kwargs)
|
log(*args, level=level, **kwargs)
|
||||||
|
|
||||||
def __enter__(self, *args, **kwargs):
|
def __enter__(self, *args, **kwargs):
|
||||||
if hasUEFI():
|
|
||||||
# on bios we don't have a boot partition
|
|
||||||
self.partition.mount(self.mountpoint)
|
|
||||||
os.makedirs(f'{self.mountpoint}/boot', exist_ok=True)
|
|
||||||
self.boot_partition.mount(f'{self.mountpoint}/boot')
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *args, **kwargs):
|
def __exit__(self, *args, **kwargs):
|
||||||
|
|
@ -119,18 +105,18 @@ class Installer():
|
||||||
if (filename := storage.get('LOG_FILE', None)):
|
if (filename := storage.get('LOG_FILE', None)):
|
||||||
absolute_logfile = os.path.join(storage.get('LOG_PATH', './'), filename)
|
absolute_logfile = os.path.join(storage.get('LOG_PATH', './'), filename)
|
||||||
|
|
||||||
if not os.path.isdir(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.mountpoint}/{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
|
return True
|
||||||
|
|
||||||
def mount(self, partition, mountpoint, create_mountpoint=True):
|
def mount(self, partition, mountpoint, create_mountpoint=True):
|
||||||
if create_mountpoint and not os.path.isdir(f'{self.mountpoint}{mountpoint}'):
|
if create_mountpoint and not os.path.isdir(f'{self.target}{mountpoint}'):
|
||||||
os.makedirs(f'{self.mountpoint}{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):
|
def post_install_check(self, *args, **kwargs):
|
||||||
return [step for step, flag in self.helper_flags.items() if flag is False]
|
return [step for step, flag in self.helper_flags.items() if flag is False]
|
||||||
|
|
@ -140,7 +126,7 @@ class Installer():
|
||||||
self.log(f'Installing packages: {packages}', level=LOG_LEVELS.Info)
|
self.log(f'Installing packages: {packages}', level=LOG_LEVELS.Info)
|
||||||
|
|
||||||
if (sync_mirrors := sys_command('/usr/bin/pacman -Syy')).exit_code == 0:
|
if (sync_mirrors := sys_command('/usr/bin/pacman -Syy')).exit_code == 0:
|
||||||
if (pacstrap := sys_command(f'/usr/bin/pacstrap {self.mountpoint} {" ".join(packages)}', peak_output=True, **kwargs)).exit_code == 0:
|
if (pacstrap := sys_command(f'/usr/bin/pacstrap {self.target} {" ".join(packages)}', **kwargs)).exit_code == 0:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.log(f'Could not strap in packages: {pacstrap.exit_code}', level=LOG_LEVELS.Info)
|
self.log(f'Could not strap in packages: {pacstrap.exit_code}', level=LOG_LEVELS.Info)
|
||||||
|
|
@ -148,42 +134,41 @@ class Installer():
|
||||||
self.log(f'Could not sync mirrors: {sync_mirrors.exit_code}', level=LOG_LEVELS.Info)
|
self.log(f'Could not sync mirrors: {sync_mirrors.exit_code}', level=LOG_LEVELS.Info)
|
||||||
|
|
||||||
def set_mirrors(self, mirrors):
|
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'):
|
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
|
fstab = sys_command(f'/usr/bin/genfstab {flags} {self.target}').trace_log
|
||||||
with open(f"{self.mountpoint}/etc/fstab", 'ab') as fstab_fh:
|
with open(f"{self.target}/etc/fstab", 'ab') as fstab_fh:
|
||||||
fstab_fh.write(fstab)
|
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}')
|
raise RequirementError(f'Could not generate fstab, strapping in packages most likely failed (disk out of space?)\n{fstab}')
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_hostname(self, hostname=None, *args, **kwargs):
|
def set_hostname(self, hostname :str, *args, **kwargs):
|
||||||
if not hostname: hostname = self.hostname
|
with open(f'{self.target}/etc/hostname', 'w') as fh:
|
||||||
with open(f'{self.mountpoint}/etc/hostname', 'w') as fh:
|
fh.write(hostname + '\n')
|
||||||
fh.write(self.hostname + '\n')
|
|
||||||
|
|
||||||
def set_locale(self, locale, encoding='UTF-8', *args, **kwargs):
|
def set_locale(self, locale, encoding='UTF-8', *args, **kwargs):
|
||||||
if not len(locale): return True
|
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')
|
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')
|
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):
|
def set_timezone(self, zone, *args, **kwargs):
|
||||||
if not zone: return True
|
if not zone: return True
|
||||||
if not len(zone): return True # Redundant
|
if not len(zone): return True # Redundant
|
||||||
|
|
||||||
if (pathlib.Path("/usr")/"share"/"zoneinfo"/zone).exists():
|
if (pathlib.Path("/usr")/"share"/"zoneinfo"/zone).exists():
|
||||||
(pathlib.Path(self.mountpoint)/"etc"/"localtime").unlink(missing_ok=True)
|
(pathlib.Path(self.target)/"etc"/"localtime").unlink(missing_ok=True)
|
||||||
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} ln -s /usr/share/zoneinfo/{zone} /etc/localtime')
|
sys_command(f'/usr/bin/arch-chroot {self.target} ln -s /usr/share/zoneinfo/{zone} /etc/localtime')
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.log(
|
self.log(
|
||||||
|
|
@ -198,12 +183,14 @@ class Installer():
|
||||||
if self.enable_service('ntpd'):
|
if self.enable_service('ntpd'):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def enable_service(self, service):
|
def enable_service(self, *services):
|
||||||
self.log(f'Enabling service {service}', level=LOG_LEVELS.Info)
|
for service in services:
|
||||||
return self.arch_chroot(f'systemctl enable {service}').exit_code == 0
|
self.log(f'Enabling service {service}', level=LOG_LEVELS.Info)
|
||||||
|
if (output := self.arch_chroot(f'systemctl enable {service}')).exit_code != 0:
|
||||||
|
raise ServiceException(f"Unable to start service {service}: {output}")
|
||||||
|
|
||||||
def run_command(self, cmd, *args, **kwargs):
|
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):
|
def arch_chroot(self, cmd, *args, **kwargs):
|
||||||
return self.run_command(cmd)
|
return self.run_command(cmd)
|
||||||
|
|
@ -223,15 +210,15 @@ class Installer():
|
||||||
|
|
||||||
conf = Networkd(Match={"Name": nic}, Network=network)
|
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))
|
netconf.write(str(conf))
|
||||||
|
|
||||||
def copy_ISO_network_config(self, enable_services=False):
|
def copy_ISO_network_config(self, enable_services=False):
|
||||||
# Copy (if any) iwd password and config files
|
# Copy (if any) iwd password and config files
|
||||||
if os.path.isdir('/var/lib/iwd/'):
|
if os.path.isdir('/var/lib/iwd/'):
|
||||||
if (psk_files := glob.glob('/var/lib/iwd/*.psk')):
|
if (psk_files := glob.glob('/var/lib/iwd/*.psk')):
|
||||||
if not os.path.isdir(f"{self.mountpoint}/var/lib/iwd"):
|
if not os.path.isdir(f"{self.target}/var/lib/iwd"):
|
||||||
os.makedirs(f"{self.mountpoint}/var/lib/iwd")
|
os.makedirs(f"{self.target}/var/lib/iwd")
|
||||||
|
|
||||||
if enable_services:
|
if enable_services:
|
||||||
# If we haven't installed the base yet (function called pre-maturely)
|
# If we haven't installed the base yet (function called pre-maturely)
|
||||||
|
|
@ -251,43 +238,67 @@ class Installer():
|
||||||
self.enable_service('iwd')
|
self.enable_service('iwd')
|
||||||
|
|
||||||
for psk in psk_files:
|
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
|
# Copy (if any) systemd-networkd config files
|
||||||
if (netconfigurations := glob.glob('/etc/systemd/network/*')):
|
if (netconfigurations := glob.glob('/etc/systemd/network/*')):
|
||||||
if not os.path.isdir(f"{self.mountpoint}/etc/systemd/network/"):
|
if not os.path.isdir(f"{self.target}/etc/systemd/network/"):
|
||||||
os.makedirs(f"{self.mountpoint}/etc/systemd/network/")
|
os.makedirs(f"{self.target}/etc/systemd/network/")
|
||||||
|
|
||||||
for netconf_file in netconfigurations:
|
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 enable_services:
|
||||||
# If we haven't installed the base yet (function called pre-maturely)
|
# If we haven't installed the base yet (function called pre-maturely)
|
||||||
if self.helper_flags.get('base', False) is False:
|
if self.helper_flags.get('base', False) is False:
|
||||||
def post_install_enable_networkd_resolved(*args, **kwargs):
|
def post_install_enable_networkd_resolved(*args, **kwargs):
|
||||||
self.enable_service('systemd-networkd')
|
self.enable_service('systemd-networkd', 'systemd-resolved')
|
||||||
self.enable_service('systemd-resolved')
|
|
||||||
|
|
||||||
self.post_base_install.append(post_install_enable_networkd_resolved)
|
self.post_base_install.append(post_install_enable_networkd_resolved)
|
||||||
# Otherwise, we can go ahead and enable the services
|
# Otherwise, we can go ahead and enable the services
|
||||||
else:
|
else:
|
||||||
self.enable_service('systemd-networkd')
|
self.enable_service('systemd-networkd', 'systemd-resolved')
|
||||||
self.enable_service('systemd-resolved')
|
|
||||||
|
|
||||||
return True
|
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):
|
def minimal_installation(self):
|
||||||
## Add necessary packages if encrypting the drive
|
## Add necessary packages if encrypting the drive
|
||||||
## (encrypted partitions default to btrfs for now, so we need btrfs-progs)
|
## (encrypted partitions default to btrfs for now, so we need btrfs-progs)
|
||||||
## TODO: Perhaps this should be living in the function which dictates
|
## TODO: Perhaps this should be living in the function which dictates
|
||||||
## the partitioning. Leaving here for now.
|
## the partitioning. Leaving here for now.
|
||||||
if self.partition.filesystem == 'btrfs':
|
MODULES = []
|
||||||
#if self.partition.encrypted:
|
BINARIES = []
|
||||||
self.base_packages.append('btrfs-progs')
|
FILES = []
|
||||||
if self.partition.filesystem == 'xfs':
|
HOOKS = ["base", "udev", "autodetect", "keyboard", "keymap", "modconf", "block", "filesystems", "fsck"]
|
||||||
self.base_packages.append('xfsprogs')
|
|
||||||
if self.partition.filesystem == 'f2fs':
|
for partition in self.partitions:
|
||||||
self.base_packages.append('f2fs-tools')
|
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.pacstrap(self.base_packages)
|
||||||
self.helper_flags['base-strapped'] = True
|
self.helper_flags['base-strapped'] = True
|
||||||
#self.genfstab()
|
#self.genfstab()
|
||||||
|
|
@ -298,39 +309,28 @@ class Installer():
|
||||||
elif vendor == "GenuineIntel":
|
elif vendor == "GenuineIntel":
|
||||||
self.base_packages.append("intel-ucode")
|
self.base_packages.append("intel-ucode")
|
||||||
else:
|
else:
|
||||||
self.log("unknown cpu vendor not installing ucode")
|
self.log("Unknown cpu vendor not installing ucode")
|
||||||
with open(f"{self.mountpoint}/etc/fstab", "a") as fstab:
|
with open(f"{self.target}/etc/fstab", "a") as fstab:
|
||||||
fstab.write(
|
fstab.write(
|
||||||
"\ntmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0\n"
|
"\ntmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0\n"
|
||||||
) # Redundant \n at the start? who knows?
|
) # Redundant \n at the start? who knows?
|
||||||
|
|
||||||
## TODO: Support locale and timezone
|
## TODO: Support locale and timezone
|
||||||
#os.remove(f'{self.mountpoint}/etc/localtime')
|
#os.remove(f'{self.target}/etc/localtime')
|
||||||
#sys_command(f'/usr/bin/arch-chroot {self.mountpoint} ln -s /usr/share/zoneinfo/{localtime} /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')
|
#sys_command('/usr/bin/arch-chroot /mnt hwclock --hctosys --localtime')
|
||||||
self.set_hostname()
|
self.set_hostname('archinstall')
|
||||||
self.set_locale('en_US')
|
self.set_locale('en_US')
|
||||||
|
|
||||||
# TODO: Use python functions for this
|
# 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.
|
with open(f'{self.target}/etc/mkinitcpio.conf', 'w') as mkinit:
|
||||||
# TODO: Yes, we should not overwrite the entire thing, but for now this should be fine
|
mkinit.write(f"MODULES=({' '.join(MODULES)})\n")
|
||||||
# since we just installed the base system.
|
mkinit.write(f"BINARIES=({' '.join(BINARIES)})\n")
|
||||||
if self.partition.filesystem == 'btrfs':
|
mkinit.write(f"FILES=({' '.join(FILES)})\n")
|
||||||
with open(f'{self.mountpoint}/etc/mkinitcpio.conf', 'w') as mkinit:
|
mkinit.write(f"HOOKS=({' '.join(HOOKS)})\n")
|
||||||
mkinit.write('MODULES=(btrfs)\n')
|
sys_command(f'/usr/bin/arch-chroot {self.target} mkinitcpio -p linux')
|
||||||
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')
|
|
||||||
|
|
||||||
self.helper_flags['base'] = True
|
self.helper_flags['base'] = True
|
||||||
|
|
||||||
|
|
@ -342,7 +342,15 @@ class Installer():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def add_bootloader(self, bootloader='systemd-bootctl'):
|
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':
|
if bootloader == 'systemd-bootctl':
|
||||||
if not hasUEFI():
|
if not hasUEFI():
|
||||||
|
|
@ -352,11 +360,11 @@ class Installer():
|
||||||
# And in which case we should do some clean up.
|
# And in which case we should do some clean up.
|
||||||
|
|
||||||
# Install the boot loader
|
# 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
|
# Modify or create a loader.conf
|
||||||
if os.path.isfile(f'{self.mountpoint}/boot/loader/loader.conf'):
|
if os.path.isfile(f'{self.target}/boot/loader/loader.conf'):
|
||||||
with open(f'{self.mountpoint}/boot/loader/loader.conf', 'r') as loader:
|
with open(f'{self.target}/boot/loader/loader.conf', 'r') as loader:
|
||||||
loader_data = loader.read().split('\n')
|
loader_data = loader.read().split('\n')
|
||||||
else:
|
else:
|
||||||
loader_data = [
|
loader_data = [
|
||||||
|
|
@ -364,7 +372,7 @@ class Installer():
|
||||||
f"timeout 5"
|
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:
|
for line in loader_data:
|
||||||
if line[:8] == 'default ':
|
if line[:8] == 'default ':
|
||||||
loader.write(f'default {self.init_time}\n')
|
loader.write(f'default {self.init_time}\n')
|
||||||
|
|
@ -375,7 +383,7 @@ class Installer():
|
||||||
## And blkid is wrong in terms of LUKS.
|
## 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 = sys_command('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip()
|
||||||
# Setup the loader entry
|
# 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 by: archinstall\n')
|
||||||
entry.write(f'# Created on: {self.init_time}\n')
|
entry.write(f'# Created on: {self.init_time}\n')
|
||||||
entry.write(f'title Arch Linux\n')
|
entry.write(f'title Arch Linux\n')
|
||||||
|
|
@ -393,37 +401,28 @@ class Installer():
|
||||||
## so we'll use the old manual method until we get that sorted out.
|
## so we'll use the old manual method until we get that sorted out.
|
||||||
|
|
||||||
|
|
||||||
if self.partition.encrypted:
|
if (real_device := self.detect_encryption(root_partition)):
|
||||||
log(f"Identifying root partition by DISK-UUID on {self.partition}, looking for '{os.path.basename(self.partition.real_device)}'.", level=LOG_LEVELS.Debug)
|
# TODO: We need to detect if the encrypted device is a whole disk encryption,
|
||||||
for root, folders, uids in os.walk('/dev/disk/by-uuid'):
|
# or simply a partition encryption. Right now we assume it's a partition (and we always have)
|
||||||
for uid in uids:
|
log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.uuid}'.", level=LOG_LEVELS.Debug)
|
||||||
real_path = os.path.realpath(os.path.join(root, uid))
|
entry.write(f'options cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n')
|
||||||
|
|
||||||
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
|
|
||||||
else:
|
else:
|
||||||
log(f"Identifying root partition by PART-UUID on {self.partition}, looking for '{os.path.basename(self.partition.path)}'.", level=LOG_LEVELS.Debug)
|
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={self.partition.uuid} rw intel_pstate=no_hwp\n')
|
entry.write(f'options root=PARTUUID={root_partition.uuid} rw intel_pstate=no_hwp\n')
|
||||||
|
|
||||||
self.helper_flags['bootloader'] = bootloader
|
self.helper_flags['bootloader'] = bootloader
|
||||||
return True
|
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 {self.partition}, there for {self.target}/boot/loader/entries/arch.conf will be broken until fixed.")
|
||||||
elif bootloader == "grub-install":
|
elif bootloader == "grub-install":
|
||||||
if hasUEFI():
|
if hasUEFI():
|
||||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB'))
|
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB'))
|
||||||
sys_command('/usr/bin/arch-chroot grub-mkconfig -o /boot/grub/grub.cfg')
|
sys_command('/usr/bin/arch-chroot grub-mkconfig -o /boot/grub/grub.cfg')
|
||||||
else:
|
else:
|
||||||
root_device = subprocess.check_output(f'basename "$(readlink -f "/sys/class/block/{self.partition.path.strip("/dev/")}/..")',shell=True).decode().strip()
|
root_device = subprocess.check_output(f'basename "$(readlink -f "/sys/class/block/{root_partition.path.strip("/dev/")}/..")', shell=True).decode().strip()
|
||||||
if root_device == "block":
|
if root_device == "block":
|
||||||
root_device = f"{self.partition.path}"
|
root_device = f"{root_partition.path}"
|
||||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} grub-install --target=--target=i386-pc /dev/{root_device}'))
|
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} grub-install --target=--target=i386-pc /dev/{root_device}'))
|
||||||
sys_command('/usr/bin/arch-chroot grub-mkconfig -o /boot/grub/grub.cfg')
|
sys_command('/usr/bin/arch-chroot grub-mkconfig -o /boot/grub/grub.cfg')
|
||||||
else:
|
else:
|
||||||
raise RequirementError(f"Unknown (or not yet implemented) bootloader added to add_bootloader(): {bootloader}")
|
raise RequirementError(f"Unknown (or not yet implemented) bootloader added to add_bootloader(): {bootloader}")
|
||||||
|
|
@ -449,19 +448,19 @@ class Installer():
|
||||||
|
|
||||||
def enable_sudo(self, entity :str, group=False):
|
def enable_sudo(self, entity :str, group=False):
|
||||||
self.log(f'Enabling sudo permissions for {entity}.', level=LOG_LEVELS.Info)
|
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')
|
sudoers.write(f'{"%" if group else ""}{entity} ALL=(ALL) ALL\n')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def user_create(self, user :str, password=None, groups=[], sudo=False):
|
def user_create(self, user :str, password=None, groups=[], sudo=False):
|
||||||
self.log(f'Creating user {user}', level=LOG_LEVELS.Info)
|
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:
|
if password:
|
||||||
self.user_set_pw(user, password)
|
self.user_set_pw(user, password)
|
||||||
|
|
||||||
if groups:
|
if groups:
|
||||||
for group in 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):
|
if sudo and self.enable_sudo(user):
|
||||||
self.helper_flags['user'] = True
|
self.helper_flags['user'] = True
|
||||||
|
|
@ -473,12 +472,12 @@ class Installer():
|
||||||
# This means the root account isn't locked/disabled with * in /etc/passwd
|
# This means the root account isn't locked/disabled with * in /etc/passwd
|
||||||
self.helper_flags['user'] = True
|
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
|
pass
|
||||||
|
|
||||||
def set_keyboard_language(self, language):
|
def set_keyboard_language(self, language):
|
||||||
if len(language.strip()):
|
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'KEYMAP={language}\n')
|
||||||
vconsole.write(f'FONT=lat9w-16\n')
|
vconsole.write(f'FONT=lat9w-16\n')
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
import time
|
||||||
|
import pathlib
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .general import *
|
from .general import *
|
||||||
from .disk import Partition
|
from .disk import Partition
|
||||||
|
|
@ -114,7 +116,7 @@ class luks2():
|
||||||
|
|
||||||
def unlock(self, partition, mountpoint, key_file):
|
def unlock(self, partition, mountpoint, key_file):
|
||||||
"""
|
"""
|
||||||
Mounts a lukts2 compatible partition to a certain mountpoint.
|
Mounts a luks2 compatible partition to a certain mountpoint.
|
||||||
Keyfile must be specified as there's no way to interact with the pw-prompt atm.
|
Keyfile must be specified as there's no way to interact with the pw-prompt atm.
|
||||||
|
|
||||||
:param mountpoint: The name without absolute path, for instance "luksdev" will point to /dev/mapper/luksdev
|
:param mountpoint: The name without absolute path, for instance "luksdev" will point to /dev/mapper/luksdev
|
||||||
|
|
@ -123,6 +125,11 @@ class luks2():
|
||||||
from .disk import get_filesystem_type
|
from .disk import get_filesystem_type
|
||||||
if '/' in mountpoint:
|
if '/' in mountpoint:
|
||||||
os.path.basename(mountpoint) # TODO: Raise exception instead?
|
os.path.basename(mountpoint) # TODO: Raise exception instead?
|
||||||
|
|
||||||
|
wait_timer = time.time()
|
||||||
|
while pathlib.Path(partition.path).exists() is False and time.time() - wait_timer < 10:
|
||||||
|
time.sleep(0.025)
|
||||||
|
|
||||||
sys_command(f'/usr/bin/cryptsetup open {partition.path} {mountpoint} --key-file {os.path.abspath(key_file)} --type luks2')
|
sys_command(f'/usr/bin/cryptsetup open {partition.path} {mountpoint} --key-file {os.path.abspath(key_file)} --type luks2')
|
||||||
if os.path.islink(f'/dev/mapper/{mountpoint}'):
|
if os.path.islink(f'/dev/mapper/{mountpoint}'):
|
||||||
self.mapdev = f'/dev/mapper/{mountpoint}'
|
self.mapdev = f'/dev/mapper/{mountpoint}'
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,52 @@ class Profile(Script):
|
||||||
if hasattr(imported, '_prep_function'):
|
if hasattr(imported, '_prep_function'):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
"""
|
||||||
|
def has_post_install(self):
|
||||||
|
with open(self.path, 'r') as source:
|
||||||
|
source_data = source.read()
|
||||||
|
|
||||||
|
# Some crude safety checks, make sure the imported profile has
|
||||||
|
# a __name__ check and if so, check if it's got a _prep_function()
|
||||||
|
# we can call to ask for more user input.
|
||||||
|
#
|
||||||
|
# If the requirements are met, import with .py in the namespace to not
|
||||||
|
# trigger a traditional:
|
||||||
|
# if __name__ == 'moduleName'
|
||||||
|
if '__name__' in source_data and '_post_install' in source_data:
|
||||||
|
with self.load_instructions(namespace=f"{self.namespace}.py") as imported:
|
||||||
|
if hasattr(imported, '_post_install'):
|
||||||
|
return True
|
||||||
|
"""
|
||||||
|
|
||||||
|
def is_top_level_profile(self):
|
||||||
|
with open(self.path, 'r') as source:
|
||||||
|
source_data = source.read()
|
||||||
|
|
||||||
|
# TODO: I imagine that there is probably a better way to write this.
|
||||||
|
return 'top_level_profile = True' in source_data
|
||||||
|
|
||||||
|
@property
|
||||||
|
def packages(self) -> list:
|
||||||
|
"""
|
||||||
|
Returns a list of packages baked into the profile definition.
|
||||||
|
If no package definition has been done, .packages() will return None.
|
||||||
|
"""
|
||||||
|
with open(self.path, 'r') as source:
|
||||||
|
source_data = source.read()
|
||||||
|
|
||||||
|
# Some crude safety checks, make sure the imported profile has
|
||||||
|
# a __name__ check before importing.
|
||||||
|
#
|
||||||
|
# If the requirements are met, import with .py in the namespace to not
|
||||||
|
# trigger a traditional:
|
||||||
|
# if __name__ == 'moduleName'
|
||||||
|
if '__name__' in source_data and '__packages__' in source_data:
|
||||||
|
with self.load_instructions(namespace=f"{self.namespace}.py") as imported:
|
||||||
|
if hasattr(imported, '__packages__'):
|
||||||
|
return imported.__packages__
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def has_post_install(self):
|
def has_post_install(self):
|
||||||
with open(self.path, 'r') as source:
|
with open(self.path, 'r') as source:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import getpass, pathlib, os, shutil, re
|
import getpass, pathlib, os, shutil, re
|
||||||
|
import sys, time, signal
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .profiles import Profile
|
from .profiles import Profile
|
||||||
from .locale_helpers import search_keyboard_layout
|
from .locale_helpers import search_keyboard_layout
|
||||||
|
|
@ -21,14 +22,49 @@ def get_longest_option(options):
|
||||||
return max([len(x) for x in options])
|
return max([len(x) for x in options])
|
||||||
|
|
||||||
def check_for_correct_username(username):
|
def check_for_correct_username(username):
|
||||||
if re.match(r'^[a-z_][a-z0-9_-]*\$?$', username) and len(username) <= 32:
|
if re.match(r'^[a-z_][a-z0-9_-]*\$?$', username) and len(username) <= 32:
|
||||||
return True
|
return True
|
||||||
log(
|
log(
|
||||||
"The username you entered is invalid. Try again",
|
"The username you entered is invalid. Try again",
|
||||||
level=LOG_LEVELS.Warning,
|
level=LOG_LEVELS.Warning,
|
||||||
fg='red'
|
fg='red'
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def do_countdown():
|
||||||
|
SIG_TRIGGER = False
|
||||||
|
def kill_handler(sig, frame):
|
||||||
|
print()
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
def sig_handler(sig, frame):
|
||||||
|
global SIG_TRIGGER
|
||||||
|
SIG_TRIGGER = True
|
||||||
|
signal.signal(signal.SIGINT, kill_handler)
|
||||||
|
|
||||||
|
original_sigint_handler = signal.getsignal(signal.SIGINT)
|
||||||
|
signal.signal(signal.SIGINT, sig_handler)
|
||||||
|
|
||||||
|
for i in range(5, 0, -1):
|
||||||
|
print(f"{i}", end='')
|
||||||
|
|
||||||
|
for x in range(4):
|
||||||
|
sys.stdout.flush()
|
||||||
|
time.sleep(0.25)
|
||||||
|
print(".", end='')
|
||||||
|
|
||||||
|
if SIG_TRIGGER:
|
||||||
|
abort = input('\nDo you really want to abort (y/n)? ')
|
||||||
|
if abort.strip() != 'n':
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
if SIG_TRIGGER is False:
|
||||||
|
sys.stdin.read()
|
||||||
|
SIG_TRIGGER = False
|
||||||
|
signal.signal(signal.SIGINT, sig_handler)
|
||||||
|
print()
|
||||||
|
signal.signal(signal.SIGINT, original_sigint_handler)
|
||||||
|
return True
|
||||||
|
|
||||||
def get_password(prompt="Enter a password: "):
|
def get_password(prompt="Enter a password: "):
|
||||||
while (passwd := getpass.getpass(prompt)):
|
while (passwd := getpass.getpass(prompt)):
|
||||||
|
|
@ -196,7 +232,7 @@ def generic_select(options, input_text="Select one of the above by index or abso
|
||||||
return None
|
return None
|
||||||
elif selected_option.isdigit():
|
elif selected_option.isdigit():
|
||||||
selected_option = int(selected_option)
|
selected_option = int(selected_option)
|
||||||
if selected_option >= len(options):
|
if selected_option > len(options):
|
||||||
raise RequirementError(f'Selected option "{selected_option}" is out of range')
|
raise RequirementError(f'Selected option "{selected_option}" is out of range')
|
||||||
selected_option = options[selected_option]
|
selected_option = options[selected_option]
|
||||||
elif selected_option in options:
|
elif selected_option in options:
|
||||||
|
|
@ -221,8 +257,10 @@ def select_disk(dict_o_disks):
|
||||||
if len(drives) >= 1:
|
if len(drives) >= 1:
|
||||||
for index, drive in enumerate(drives):
|
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']})")
|
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): ')
|
drive = input('Select one of the above disks (by number or full path) or write /mnt to skip partitioning: ')
|
||||||
if drive.isdigit():
|
if drive.strip() == '/mnt':
|
||||||
|
return None
|
||||||
|
elif drive.isdigit():
|
||||||
drive = int(drive)
|
drive = int(drive)
|
||||||
if drive >= len(drives):
|
if drive >= len(drives):
|
||||||
raise DiskError(f'Selected option "{drive}" is out of range')
|
raise DiskError(f'Selected option "{drive}" is out of range')
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@ Locale related
|
||||||
|
|
||||||
.. autofunction:: archinstall.set_keyboard_language
|
.. autofunction:: archinstall.set_keyboard_language
|
||||||
|
|
||||||
|
..
|
||||||
|
autofunction:: archinstall.Installer.set_keyboard_layout
|
||||||
|
|
||||||
Services
|
Services
|
||||||
========
|
========
|
||||||
|
|
||||||
|
|
@ -88,3 +91,13 @@ Exceptions
|
||||||
.. autofunction:: archinstall.ProfileError
|
.. autofunction:: archinstall.ProfileError
|
||||||
|
|
||||||
.. autofunction:: archinstall.SysCallError
|
.. autofunction:: archinstall.SysCallError
|
||||||
|
|
||||||
|
.. autofunction:: archinstall.ProfileNotFound
|
||||||
|
|
||||||
|
.. autofunction:: archinstall.HardwareIncompatibilityError
|
||||||
|
|
||||||
|
.. autofunction:: archinstall.PermissionError
|
||||||
|
|
||||||
|
.. autofunction:: archinstall.UserError
|
||||||
|
|
||||||
|
.. autofunction:: archinstall.ServiceException
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ python-archinstall Documentation
|
||||||
================================
|
================================
|
||||||
|
|
||||||
| **python-archinstall** *(or, archinstall for short)* is a helper library to install Arch Linux and manage services, packages and other things.
|
| **python-archinstall** *(or, archinstall for short)* is a helper library to install Arch Linux and manage services, packages and other things.
|
||||||
| It comes packaged with different pre-configured installers, such as the :ref:`guided <installing.guided>` installer.
|
| It comes packaged with different pre-configured installers, such as the `Guided installation`_ installer.
|
||||||
|
|
|
|
||||||
| A demo can be viewed here: `https://www.youtube.com/watch?v=9Xt7X_Iqg6E <https://www.youtube.com/watch?v=9Xt7X_Iqg6E>`_ which uses the default guided installer.
|
| A demo can be viewed here: `https://www.youtube.com/watch?v=9Xt7X_Iqg6E <https://www.youtube.com/watch?v=9Xt7X_Iqg6E>`_ which uses the default guided installer.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import getpass, time, json, sys, signal, os
|
import getpass, time, json, os
|
||||||
import archinstall
|
import archinstall
|
||||||
from archinstall.lib.hardware import hasUEFI
|
from archinstall.lib.hardware import hasUEFI
|
||||||
from archinstall.lib.profiles import Profile
|
from archinstall.lib.profiles import Profile
|
||||||
|
|
@ -52,12 +52,14 @@ def ask_user_questions():
|
||||||
archinstall.arguments['harddrive'] = archinstall.BlockDevice(archinstall.arguments['harddrive'])
|
archinstall.arguments['harddrive'] = archinstall.BlockDevice(archinstall.arguments['harddrive'])
|
||||||
else:
|
else:
|
||||||
archinstall.arguments['harddrive'] = archinstall.select_disk(archinstall.all_disks())
|
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.
|
# Perform a quick sanity check on the selected harddrive.
|
||||||
# 1. Check if it has partitions
|
# 1. Check if it has partitions
|
||||||
# 3. Check that we support the current partitions
|
# 3. Check that we support the current partitions
|
||||||
# 2. If so, ask if we should keep them or wipe everything
|
# 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')
|
archinstall.log(f"{archinstall.arguments['harddrive']} contains the following partitions:", fg='yellow')
|
||||||
|
|
||||||
# We curate a list pf supported partitions
|
# We curate a list pf supported partitions
|
||||||
|
|
@ -136,14 +138,14 @@ def ask_user_questions():
|
||||||
elif option == 'format-all':
|
elif option == 'format-all':
|
||||||
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
||||||
archinstall.arguments['harddrive'].keep_partitions = False
|
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
|
# If the drive doesn't have any partitions, safely mark the disk with keep_partitions = False
|
||||||
# and ask the user for a root filesystem.
|
# and ask the user for a root filesystem.
|
||||||
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
||||||
archinstall.arguments['harddrive'].keep_partitions = False
|
archinstall.arguments['harddrive'].keep_partitions = False
|
||||||
|
|
||||||
# Get disk encryption password (or skip if blank)
|
# 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): ')):
|
if (passwd := archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ')):
|
||||||
archinstall.arguments['!encryption-password'] = passwd
|
archinstall.arguments['!encryption-password'] = passwd
|
||||||
archinstall.arguments['harddrive'].encryption_password = archinstall.arguments['!encryption-password']
|
archinstall.arguments['harddrive'].encryption_password = archinstall.arguments['!encryption-password']
|
||||||
|
|
@ -199,12 +201,14 @@ def ask_user_questions():
|
||||||
print("If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt.")
|
print("If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt.")
|
||||||
archinstall.arguments['packages'] = [package for package in input('Write additional packages to install (space separated, leave blank to skip): ').split(' ') if len(package)]
|
archinstall.arguments['packages'] = [package for package in input('Write additional packages to install (space separated, leave blank to skip): ').split(' ') if len(package)]
|
||||||
|
|
||||||
# Verify packages that were given
|
if len(archinstall.arguments['packages']):
|
||||||
try:
|
# Verify packages that were given
|
||||||
archinstall.validate_package_list(archinstall.arguments['packages'])
|
try:
|
||||||
except archinstall.RequirementError as e:
|
archinstall.log(f"Verifying that additional packages exist (this might take a few seconds)")
|
||||||
archinstall.log(e, fg='red')
|
archinstall.validate_package_list(archinstall.arguments['packages'])
|
||||||
exit(1)
|
except archinstall.RequirementError as e:
|
||||||
|
archinstall.log(e, fg='red')
|
||||||
|
exit(1)
|
||||||
|
|
||||||
# Ask or Call the helper function that asks the user to optionally configure a network.
|
# Ask or Call the helper function that asks the user to optionally configure a network.
|
||||||
if not archinstall.arguments.get('nic', None):
|
if not archinstall.arguments.get('nic', None):
|
||||||
|
|
@ -217,8 +221,6 @@ def ask_user_questions():
|
||||||
|
|
||||||
|
|
||||||
def perform_installation_steps():
|
def perform_installation_steps():
|
||||||
global SIG_TRIGGER
|
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print('This is your chosen configuration:')
|
print('This is your chosen configuration:')
|
||||||
archinstall.log("-- Guided template chosen (with below config) --", level=archinstall.LOG_LEVELS.Debug)
|
archinstall.log("-- Guided template chosen (with below config) --", level=archinstall.LOG_LEVELS.Debug)
|
||||||
|
|
@ -232,89 +234,67 @@ def perform_installation_steps():
|
||||||
We mention the drive one last time, and count from 5 to 0.
|
We mention the drive one last time, and count from 5 to 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
print(f" ! Formatting {archinstall.arguments['harddrive']} in ", end='')
|
if archinstall.arguments.get('harddrive', None):
|
||||||
|
print(f" ! Formatting {archinstall.arguments['harddrive']} in ", end='')
|
||||||
|
archinstall.do_countdown()
|
||||||
|
|
||||||
for i in range(5, 0, -1):
|
"""
|
||||||
print(f"{i}", end='')
|
Setup the blockdevice, filesystem (and optionally encryption).
|
||||||
|
Once that's done, we'll hand over to perform_installation()
|
||||||
for x in range(4):
|
"""
|
||||||
sys.stdout.flush()
|
|
||||||
time.sleep(0.25)
|
|
||||||
print(".", end='')
|
|
||||||
|
|
||||||
if SIG_TRIGGER:
|
|
||||||
abort = input('\nDo you really want to abort (y/n)? ')
|
|
||||||
if abort.strip() != 'n':
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
if SIG_TRIGGER is False:
|
|
||||||
sys.stdin.read()
|
|
||||||
SIG_TRIGGER = False
|
|
||||||
signal.signal(signal.SIGINT, sig_handler)
|
|
||||||
|
|
||||||
# Put back the default/original signal handler now that we're done catching
|
|
||||||
# and interrupting SIGINT with "Do you really want to abort".
|
|
||||||
print()
|
|
||||||
signal.signal(signal.SIGINT, original_sigint_handler)
|
|
||||||
|
|
||||||
"""
|
|
||||||
Setup the blockdevice, filesystem (and optionally encryption).
|
|
||||||
Once that's done, we'll hand over to perform_installation()
|
|
||||||
"""
|
|
||||||
# maybe we can ask the user what they would prefer on uefi systems?
|
|
||||||
if hasUEFI():
|
|
||||||
mode = archinstall.GPT
|
mode = archinstall.GPT
|
||||||
else:
|
if hasUEFI() is False:
|
||||||
mode = archinstall.MBR
|
mode = archinstall.MBR
|
||||||
with archinstall.Filesystem(archinstall.arguments['harddrive'],mode) as fs:
|
|
||||||
# Wipe the entire drive if the disk flag `keep_partitions`is False.
|
with archinstall.Filesystem(archinstall.arguments['harddrive'], mode) as fs:
|
||||||
if archinstall.arguments['harddrive'].keep_partitions is False:
|
# Wipe the entire drive if the disk flag `keep_partitions`is False.
|
||||||
fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'))
|
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):
|
# Check if encryption is desired and mark the root partition as encrypted.
|
||||||
root_partition = fs.find_partition('/')
|
if archinstall.arguments.get('!encryption-password', None):
|
||||||
root_partition.encrypted = True
|
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.
|
# After the disk is ready, iterate the partitions and check
|
||||||
for partition in archinstall.arguments['harddrive']:
|
# which ones are safe to format, and format those.
|
||||||
if partition.safe_to_format():
|
for partition in archinstall.arguments['harddrive']:
|
||||||
# Partition might be marked as encrypted due to the filesystem type crypt_LUKS
|
if partition.safe_to_format():
|
||||||
# But we might have omitted the encryption password question to skip encryption.
|
# Partition might be marked as encrypted due to the filesystem type crypt_LUKS
|
||||||
# In which case partition.encrypted will be true, but passwd will be false.
|
# But we might have omitted the encryption password question to skip encryption.
|
||||||
if partition.encrypted and (passwd := archinstall.arguments.get('!encryption-password', None)):
|
# In which case partition.encrypted will be true, but passwd will be false.
|
||||||
partition.encrypt(password=passwd)
|
if partition.encrypted and (passwd := archinstall.arguments.get('!encryption-password', None)):
|
||||||
|
partition.encrypt(password=passwd)
|
||||||
|
else:
|
||||||
|
partition.format()
|
||||||
else:
|
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:
|
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):
|
fs.find_partition('/boot').mount('/mnt/boot')
|
||||||
# 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
|
perform_installation('/mnt')
|
||||||
# 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'])
|
|
||||||
|
|
||||||
|
|
||||||
def perform_installation(device, boot_partition, language, mirrors):
|
def perform_installation(mountpoint):
|
||||||
"""
|
"""
|
||||||
Performs the installation steps on a block device.
|
Performs the installation steps on a block device.
|
||||||
Only requirement is that the block devices are
|
Only requirement is that the block devices are
|
||||||
formatted and setup prior to entering this function.
|
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):
|
## if len(mirrors):
|
||||||
# Certain services might be running that affects the system during installation.
|
# 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
|
# Currently, only one such service is "reflector.service" which updates /etc/pacman.d/mirrorlist
|
||||||
|
|
@ -323,10 +303,11 @@ def perform_installation(device, boot_partition, language, mirrors):
|
||||||
while 'dead' not in (status := archinstall.service_state('reflector')):
|
while 'dead' not in (status := archinstall.service_state('reflector')):
|
||||||
time.sleep(1)
|
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():
|
if installation.minimal_installation():
|
||||||
installation.set_mirrors(mirrors) # Set the mirrors in the installation medium
|
installation.set_hostname(archinstall.arguments['hostname'])
|
||||||
installation.set_keyboard_language(language)
|
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()
|
installation.add_bootloader()
|
||||||
|
|
||||||
# If user selected to copy the current ISO network configuration
|
# If user selected to copy the current ISO network configuration
|
||||||
|
|
@ -346,7 +327,8 @@ def perform_installation(device, boot_partition, language, mirrors):
|
||||||
installation.log(f"The {archinstall.arguments.get('audio', None)} audio server will be used.", level=archinstall.LOG_LEVELS.Info)
|
installation.log(f"The {archinstall.arguments.get('audio', None)} audio server will be used.", level=archinstall.LOG_LEVELS.Info)
|
||||||
if archinstall.arguments.get('audio', None) == 'pipewire':
|
if archinstall.arguments.get('audio', None) == 'pipewire':
|
||||||
print('Installing pipewire ...')
|
print('Installing pipewire ...')
|
||||||
installation.add_additional_packages(["pipewire", "pipewire-alsa", "pipewire-docs", "pipewire-jack", "pipewire-media-session", "pipewire-pulse", "gst-plugin-pipewire", "libpulse"])
|
|
||||||
|
installation.add_additional_packages(["pipewire", "pipewire-alsa", "pipewire-jack", "pipewire-media-session", "pipewire-pulse", "gst-plugin-pipewire", "libpulse"])
|
||||||
elif archinstall.arguments.get('audio', None) == 'pulseaudio':
|
elif archinstall.arguments.get('audio', None) == 'pulseaudio':
|
||||||
print('Installing pulseaudio ...')
|
print('Installing pulseaudio ...')
|
||||||
installation.add_additional_packages("pulseaudio")
|
installation.add_additional_packages("pulseaudio")
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,71 @@
|
||||||
import archinstall, getpass
|
import archinstall
|
||||||
|
|
||||||
# Unmount and close previous runs
|
|
||||||
archinstall.sys_command(f'umount -R /mnt', suppress_errors=True)
|
|
||||||
archinstall.sys_command(f'cryptsetup close /dev/mapper/luksloop', suppress_errors=True)
|
|
||||||
|
|
||||||
# Select a harddrive and a disk password
|
# Select a harddrive and a disk password
|
||||||
harddrive = archinstall.select_disk(archinstall.all_disks())
|
archinstall.log(f"Minimal only supports:")
|
||||||
disk_password = getpass.getpass(prompt='Disk password (won\'t echo): ')
|
archinstall.log(f" * Being installed to a single disk")
|
||||||
|
|
||||||
with archinstall.Filesystem(harddrive) as fs:
|
if archinstall.arguments.get('help', None):
|
||||||
# Use the entire disk instead of setting up partitions on your own
|
archinstall.log(f" - Optional disk encryption via --!encryption-password=<password>")
|
||||||
fs.use_entire_disk('luks2')
|
archinstall.log(f" - Optional filesystem type via --filesystem=<fs type>")
|
||||||
|
archinstall.log(f" - Optional systemd network via --network")
|
||||||
|
|
||||||
if harddrive.partition[1].size == '512M':
|
archinstall.arguments['harddrive'] = archinstall.select_disk(archinstall.all_disks())
|
||||||
raise OSError('Trying to encrypt the boot partition for petes sake..')
|
|
||||||
harddrive.partition[0].format('fat32')
|
|
||||||
|
|
||||||
with archinstall.luks2(harddrive.partition[1], 'luksloop', disk_password) as unlocked_device:
|
def install_on(mountpoint):
|
||||||
unlocked_device.format('btrfs')
|
# We kick off the installer by telling it where the
|
||||||
|
with archinstall.Installer(mountpoint) as installation:
|
||||||
with archinstall.Installer(unlocked_device, boot_partition=harddrive.partition[0], hostname='testmachine') as installation:
|
# Strap in the base system, add a boot loader and configure
|
||||||
if installation.minimal_installation():
|
# some other minor details as specified by this profile and user.
|
||||||
installation.add_bootloader()
|
if installation.minimal_installation():
|
||||||
|
installation.set_hostname('minimal-arch')
|
||||||
|
installation.add_bootloader()
|
||||||
|
|
||||||
installation.add_additional_packages(['nano', 'wget', 'git'])
|
# Optionally enable networking:
|
||||||
installation.install_profile('minimal')
|
if archinstall.arguments.get('network', None):
|
||||||
|
installation.copy_ISO_network_config(enable_services=True)
|
||||||
|
|
||||||
installation.user_create('devel', 'devel')
|
installation.add_additional_packages(['nano', 'wget', 'git'])
|
||||||
installation.user_set_pw('root', 'toor')
|
installation.install_profile('minimal')
|
||||||
|
|
||||||
|
installation.user_create('devel', 'devel')
|
||||||
|
installation.user_set_pw('root', 'airoot')
|
||||||
|
|
||||||
|
# Once this is done, we output some useful information to the user
|
||||||
|
# And the installation is complete.
|
||||||
|
archinstall.log(f"There are two new accounts in your installation after reboot:")
|
||||||
|
archinstall.log(f" * root (password: airoot)")
|
||||||
|
archinstall.log(f" * devel (password: devel)")
|
||||||
|
|
||||||
|
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'))
|
||||||
|
|
||||||
|
boot = fs.find_partition('/boot')
|
||||||
|
root = fs.find_partition('/')
|
||||||
|
|
||||||
|
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.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)
|
||||||
|
unlocked_root.mount('/mnt')
|
||||||
|
else:
|
||||||
|
root.format(root.filesystem)
|
||||||
|
root.mount('/mnt')
|
||||||
|
|
||||||
|
boot.mount('/mnt/boot')
|
||||||
|
|
||||||
|
install_on('/mnt')
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
# "It is recommended also to install the gnome group, which contains applications required for the standard GNOME experience." - Arch Wiki
|
||||||
|
installation.add_additional_packages("budgie-desktop lightdm lightdm-gtk-greeter gnome")
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import archinstall
|
import archinstall
|
||||||
|
|
||||||
installation.add_additional_packages("cinnamon system-config-printer gnome-keyring gnome-terminal blueberry metacity lightdm lightdm-gtk-greeter lightdm-gtk-greeter-settings")
|
installation.add_additional_packages("cinnamon system-config-printer gnome-keyring gnome-terminal blueberry metacity lightdm lightdm-gtk-greeter")
|
||||||
# We'll create a cinnamon-minimal later, but for now, we'll avoid issues by giving more than we need.
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
import archinstall
|
import archinstall
|
||||||
installation.add_additional_packages("i3lock i3status i3blocks i3-gaps")
|
installation.add_additional_packages("i3-gaps")
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
import archinstall
|
import archinstall
|
||||||
installation.add_additional_packages("i3lock i3status i3blocks i3-wm")
|
installation.add_additional_packages("i3-wm")
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
installation.add_additional_packages("lxqt breeze-icons oxygen-icons xdg-utils ttf-freefont leafpad slock archlinux-wallpaper sddm")
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
installation.add_additional_packages("mate mate-extra lightdm lightdm-gtk-greeter")
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
import archinstall
|
import archinstall
|
||||||
packages = "sway swaylock swayidle dmenu alacritty"
|
__packages__ = "sway swaylock swayidle waybar dmenu light grim slurp pavucontrol alacritty"
|
||||||
installation.add_additional_packages(packages)
|
installation.add_additional_packages(__packages__)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import archinstall
|
import archinstall
|
||||||
|
__packages__ = "xfce4 xfce4-goodies lightdm lightdm-gtk-greeter"
|
||||||
installation.add_additional_packages("xfce4 xfce4-goodies lightdm lightdm-gtk-greeter lightdm-gtk-greeter-settings")
|
installation.add_additional_packages(__packages__)
|
||||||
# We'll create a xfce4-minimal later, but for now, we'll avoid issues by giving more than we need.
|
|
||||||
|
|
@ -6,7 +6,7 @@ is_top_level_profile = False
|
||||||
|
|
||||||
# New way of defining packages for a profile, which is iterable and can be used out side
|
# New way of defining packages for a profile, which is iterable and can be used out side
|
||||||
# of the profile to get a list of "what packages will be installed".
|
# of the profile to get a list of "what packages will be installed".
|
||||||
__packages__ = ['nano', 'nemo', 'gpicview-gtk3', 'openssh', 'sshfs', 'htop', 'scrot', 'wget']
|
__packages__ = ['nemo', 'gpicview-gtk3', 'scrot']
|
||||||
|
|
||||||
def _prep_function(*args, **kwargs):
|
def _prep_function(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
<<<<<<< HEAD:profiles/cinnamon.py
|
||||||
|
# A desktop environment using "Cinnamon"
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
=======
|
||||||
|
# A desktop environment using "budgie"
|
||||||
|
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
>>>>>>> master:profiles/budgie.py
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
|
def _prep_function(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Magic function called by the importing installer
|
||||||
|
before continuing any further. It also avoids executing any
|
||||||
|
other code in this stage. So it's a safe way to ask the user
|
||||||
|
for more input before any other installer steps start.
|
||||||
|
"""
|
||||||
|
|
||||||
|
<<<<<<< HEAD:profiles/cinnamon.py
|
||||||
|
# Cinnamon requires a functioning Xorg installation.
|
||||||
|
=======
|
||||||
|
# budgie requires a functioning Xorg installation.
|
||||||
|
>>>>>>> master:profiles/budgie.py
|
||||||
|
profile = archinstall.Profile(None, 'xorg')
|
||||||
|
with profile.load_instructions(namespace='xorg.py') as imported:
|
||||||
|
if hasattr(imported, '_prep_function'):
|
||||||
|
return imported._prep_function()
|
||||||
|
else:
|
||||||
|
print('Deprecated (??): xorg profile has no _prep_function() anymore')
|
||||||
|
|
||||||
|
# Ensures that this code only gets executed if executed
|
||||||
|
<<<<<<< HEAD:profiles/cinnamon.py
|
||||||
|
# through importlib.util.spec_from_file_location("cinnamon", "/somewhere/cinnamon.py")
|
||||||
|
# or through conventional import cinnamon
|
||||||
|
if __name__ == 'cinnamon':
|
||||||
|
# Install dependency profiles
|
||||||
|
installation.install_profile('xorg')
|
||||||
|
|
||||||
|
# Install the application cinnamon from the template under /applications/
|
||||||
|
cinnamon = archinstall.Application(installation, 'cinnamon')
|
||||||
|
cinnamon.install()
|
||||||
|
=======
|
||||||
|
# through importlib.util.spec_from_file_location("budgie", "/somewhere/budgie.py")
|
||||||
|
# or through conventional import budgie
|
||||||
|
if __name__ == 'budgie':
|
||||||
|
# Install dependency profiles
|
||||||
|
installation.install_profile('xorg')
|
||||||
|
|
||||||
|
# Install the application budgie from the template under /applications/
|
||||||
|
budgie = archinstall.Application(installation, 'budgie')
|
||||||
|
budgie.install()
|
||||||
|
>>>>>>> master:profiles/budgie.py
|
||||||
|
|
||||||
|
installation.enable_service('lightdm') # Light Display Manager
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
# A desktop environment using "Cinnamon"
|
# A desktop environment using "Cinnamon"
|
||||||
|
|
||||||
import archinstall
|
import archinstall
|
||||||
|
|
||||||
is_top_level_profile = False
|
is_top_level_profile = False
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@ import archinstall, os
|
||||||
|
|
||||||
is_top_level_profile = True
|
is_top_level_profile = True
|
||||||
|
|
||||||
|
# New way of defining packages for a profile, which is iterable and can be used out side
|
||||||
|
# of the profile to get a list of "what packages will be installed".
|
||||||
|
__packages__ = ['nano', 'vim', 'openssh', 'htop', 'wget', 'iwd', 'wireless_tools', 'wpa_supplicant', 'smartmontools', 'xdg-utils']
|
||||||
|
|
||||||
def _prep_function(*args, **kwargs):
|
def _prep_function(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Magic function called by the importing installer
|
Magic function called by the importing installer
|
||||||
|
|
@ -12,9 +16,9 @@ def _prep_function(*args, **kwargs):
|
||||||
for more input before any other installer steps start.
|
for more input before any other installer steps start.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
supported_desktops = ['gnome', 'kde', 'awesome', 'sway', 'cinnamon', 'xfce4', 'lxqt', 'i3', 'budgie']
|
supported_desktops = ['gnome', 'kde', 'awesome', 'sway', 'cinnamon', 'xfce4', 'lxqt', 'i3', 'budgie', 'mate']
|
||||||
desktop = archinstall.generic_select(supported_desktops, 'Select your desired desktop environment: ')
|
desktop = archinstall.generic_select(supported_desktops, 'Select your desired desktop environment: ')
|
||||||
|
|
||||||
# Temporarily store the selected desktop profile
|
# Temporarily store the selected desktop profile
|
||||||
# in a session-safe location, since this module will get reloaded
|
# in a session-safe location, since this module will get reloaded
|
||||||
# the next time it gets executed.
|
# the next time it gets executed.
|
||||||
|
|
@ -41,7 +45,11 @@ if __name__ == 'desktop':
|
||||||
There are plenty of desktop-turn-key-solutions based on Arch Linux,
|
There are plenty of desktop-turn-key-solutions based on Arch Linux,
|
||||||
this is therefore just a helper to get started
|
this is therefore just a helper to get started
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Install common packages for all desktop environments
|
||||||
|
installation.add_additional_packages(__packages__)
|
||||||
|
|
||||||
# TODO: Remove magic variable 'installation' and place it
|
# TODO: Remove magic variable 'installation' and place it
|
||||||
# in archinstall.storage or archinstall.session/archinstall.installation
|
# in archinstall.storage or archinstall.session/archinstall.installation
|
||||||
installation.install_profile(archinstall.storage['_desktop_profile'])
|
installation.install_profile(archinstall.storage['_desktop_profile'])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
# Common package for i3, lets user select which i3 configuration they want.
|
||||||
|
|
||||||
|
import archinstall, os
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
|
# New way of defining packages for a profile, which is iterable and can be used out side
|
||||||
|
# of the profile to get a list of "what packages will be installed".
|
||||||
|
__packages__ = ['i3lock', 'i3status', 'i3blocks', 'xterm']
|
||||||
|
|
||||||
|
def _prep_function(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Magic function called by the importing installer
|
||||||
|
before continuing any further. It also avoids executing any
|
||||||
|
other code in this stage. So it's a safe way to ask the user
|
||||||
|
for more input before any other installer steps start.
|
||||||
|
"""
|
||||||
|
|
||||||
|
supported_configurations = ['i3-wm', 'i3-gaps']
|
||||||
|
desktop = archinstall.generic_select(supported_configurations, 'Select your desired configuration: ')
|
||||||
|
|
||||||
|
# Temporarily store the selected desktop profile
|
||||||
|
# in a session-safe location, since this module will get reloaded
|
||||||
|
# the next time it gets executed.
|
||||||
|
archinstall.storage['_i3_configuration'] = desktop
|
||||||
|
|
||||||
|
# i3 requires a functioning Xorg installation.
|
||||||
|
profile = archinstall.Profile(None, 'xorg')
|
||||||
|
with profile.load_instructions(namespace='xorg.py') as imported:
|
||||||
|
if hasattr(imported, '_prep_function'):
|
||||||
|
return imported._prep_function()
|
||||||
|
else:
|
||||||
|
print('Deprecated (??): xorg profile has no _prep_function() anymore')
|
||||||
|
|
||||||
|
if __name__ == 'i3':
|
||||||
|
"""
|
||||||
|
This "profile" is a meta-profile.
|
||||||
|
There are no desktop-specific steps, it simply routes
|
||||||
|
the installer to whichever desktop environment/window manager was chosen.
|
||||||
|
|
||||||
|
Maybe in the future, a network manager or similar things *could* be added here.
|
||||||
|
We should honor that Arch Linux does not officially endorse a desktop-setup, nor is
|
||||||
|
it trying to be a turn-key desktop distribution.
|
||||||
|
|
||||||
|
There are plenty of desktop-turn-key-solutions based on Arch Linux,
|
||||||
|
this is therefore just a helper to get started
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Install common packages for all i3 configurations
|
||||||
|
installation.add_additional_packages(__packages__)
|
||||||
|
|
||||||
|
# Install dependency profiles
|
||||||
|
installation.install_profile('xorg')
|
||||||
|
|
||||||
|
# gaps is installed by deafult so we are overriding it here
|
||||||
|
installation.add_additional_packages("lightdm-gtk-greeter lightdm")
|
||||||
|
|
||||||
|
# Auto start lightdm for all users
|
||||||
|
installation.enable_service('lightdm')
|
||||||
|
|
||||||
|
# install the i3 group now
|
||||||
|
i3 = archinstall.Application(installation, archinstall.storage['_i3_configuration'])
|
||||||
|
i3.install()
|
||||||
|
|
@ -22,11 +22,13 @@ def _prep_function(*args, **kwargs):
|
||||||
else:
|
else:
|
||||||
print('Deprecated (??): xorg profile has no _prep_function() anymore')
|
print('Deprecated (??): xorg profile has no _prep_function() anymore')
|
||||||
|
|
||||||
|
"""
|
||||||
def _post_install(*args, **kwargs):
|
def _post_install(*args, **kwargs):
|
||||||
if "nvidia" in _gfx_driver_packages:
|
if "nvidia" in _gfx_driver_packages:
|
||||||
print("Plasma Wayland has known compatibility issues with the proprietary Nvidia driver")
|
print("Plasma Wayland has known compatibility issues with the proprietary Nvidia driver")
|
||||||
print("After booting, you can choose between Wayland and Xorg using the drop-down menu")
|
print("After booting, you can choose between Wayland and Xorg using the drop-down menu")
|
||||||
return True
|
return True
|
||||||
|
"""
|
||||||
|
|
||||||
# Ensures that this code only gets executed if executed
|
# Ensures that this code only gets executed if executed
|
||||||
# through importlib.util.spec_from_file_location("kde", "/somewhere/kde.py")
|
# through importlib.util.spec_from_file_location("kde", "/somewhere/kde.py")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
|
||||||
|
# A desktop environment using "LXQt"
|
||||||
|
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
|
def _prep_function(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Magic function called by the importing installer
|
||||||
|
before continuing any further. It also avoids executing any
|
||||||
|
other code in this stage. So it's a safe way to ask the user
|
||||||
|
for more input before any other installer steps start.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# LXQt requires a functional xorg installation.
|
||||||
|
profile = archinstall.Profile(None, 'xorg')
|
||||||
|
with profile.load_instructions(namespace='xorg.py') as imported:
|
||||||
|
if hasattr(imported, '_prep_function'):
|
||||||
|
return imported._prep_function()
|
||||||
|
else:
|
||||||
|
print('Deprecated (??): xorg profile has no _prep_function() anymore')
|
||||||
|
|
||||||
|
# Ensures that this code only gets executed if executed
|
||||||
|
# through importlib.util.spec_from_file_location("lxqt", "/somewhere/lxqt.py")
|
||||||
|
# or through conventional import lxqt
|
||||||
|
if __name__ == 'lxqt':
|
||||||
|
# Install dependency profiles
|
||||||
|
installation.install_profile('xorg')
|
||||||
|
|
||||||
|
# Install the application xfce4 from the template under /applications/
|
||||||
|
xfce = archinstall.Application(installation, 'lxqt')
|
||||||
|
xfce.install()
|
||||||
|
|
||||||
|
installation.enable_service('sddm') # SDDM Display Manager
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
# A desktop environment using "MATE"
|
||||||
|
|
||||||
|
import archinstall
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
|
def _prep_function(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Magic function called by the importing installer
|
||||||
|
before continuing any further. It also avoids executing any
|
||||||
|
other code in this stage. So it's a safe way to ask the user
|
||||||
|
for more input before any other installer steps start.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# MATE requires a functional xorg installation.
|
||||||
|
profile = archinstall.Profile(None, 'xorg')
|
||||||
|
with profile.load_instructions(namespace='xorg.py') as imported:
|
||||||
|
if hasattr(imported, '_prep_function'):
|
||||||
|
return imported._prep_function()
|
||||||
|
else:
|
||||||
|
print('Deprecated (??): xorg profile has no _prep_function() anymore')
|
||||||
|
|
||||||
|
# Ensures that this code only gets executed if executed
|
||||||
|
# through importlib.util.spec_from_file_location("mate", "/somewhere/mate.py")
|
||||||
|
# or through conventional import mate
|
||||||
|
if __name__ == 'mate':
|
||||||
|
# Install dependency profiles
|
||||||
|
installation.install_profile('xorg')
|
||||||
|
|
||||||
|
# Install the application mate from the template under /applications/
|
||||||
|
mate = archinstall.Application(installation, 'mate')
|
||||||
|
mate.install()
|
||||||
|
|
||||||
|
installation.enable_service('lightdm') # Light Display Manager
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import archinstall, os
|
# A desktop environment using "Sway"
|
||||||
|
|
||||||
# TODO: Remove hard dependency of bash (due to .bash_profile)
|
import archinstall
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
def _prep_function(*args, **kwargs):
|
def _prep_function(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -9,37 +11,12 @@ def _prep_function(*args, **kwargs):
|
||||||
other code in this stage. So it's a safe way to ask the user
|
other code in this stage. So it's a safe way to ask the user
|
||||||
for more input before any other installer steps start.
|
for more input before any other installer steps start.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__builtins__['_gfx_driver_packages'] = archinstall.select_driver()
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _post_install(*args, **kwargs):
|
|
||||||
choice = input("Would you like to autostart sway on login [Y/n]: ")
|
|
||||||
if choice.lower == "y":
|
|
||||||
with open(f"{installation.mountpoint}/etc/profile", "a") as f:
|
|
||||||
x = """
|
|
||||||
if [ -z $DISPLAY ] && [ "$(tty)" == "/dev/tty1" ]; then
|
|
||||||
exec sway
|
|
||||||
fi
|
|
||||||
"""
|
|
||||||
f.write(x)
|
|
||||||
f.close()
|
|
||||||
else:
|
|
||||||
installation.log("To start Sway, run the 'sway' command after logging in.")
|
|
||||||
|
|
||||||
# Ensures that this code only gets executed if executed
|
# Ensures that this code only gets executed if executed
|
||||||
# through importlib.util.spec_from_file_location("kde", "/somewhere/kde.py")
|
# through importlib.util.spec_from_file_location("sway", "/somewhere/sway.py")
|
||||||
# or through conventional import kde
|
# or through conventional import sway
|
||||||
if __name__ == 'sway':
|
if __name__ == 'sway':
|
||||||
|
# Install the application sway from the template under /applications/
|
||||||
installation.add_additional_packages(_gfx_driver_packages)
|
|
||||||
|
|
||||||
# Install dependency profiles
|
|
||||||
if _gfx_driver_packages == 'nvidia':
|
|
||||||
# NOTE: This is technically runnable with the --my-next-gpu-wont-be-nvidia option
|
|
||||||
raise archinstall.lib.exceptions.HardwareIncompatibilityError("Sway does not officially support the proprietary Nvidia driver, you may have to use nouveau.")
|
|
||||||
|
|
||||||
# Install the application kde from the template under /applications/
|
|
||||||
sway = archinstall.Application(installation, 'sway')
|
sway = archinstall.Application(installation, 'sway')
|
||||||
sway.install()
|
sway.install()
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
|
|
||||||
# A desktop environment using "Xfce4"
|
# A desktop environment using "Xfce4"
|
||||||
|
|
||||||
is_top_level_profile = False
|
|
||||||
|
|
||||||
import archinstall
|
import archinstall
|
||||||
|
|
||||||
|
is_top_level_profile = False
|
||||||
|
|
||||||
def _prep_function(*args, **kwargs):
|
def _prep_function(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Magic function called by the importing installer
|
Magic function called by the importing installer
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue