Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions archinstall/lib/disk/lvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ def lvm_vol_change(vol: LvmVolume, activate: bool) -> None:
SysCommand(cmd)


def lvm_vg_change(vg: LvmVolumeGroup, activate: bool) -> None:
active_flag = 'y' if activate else 'n'
cmd = ['vgchange', '-a', active_flag, vg.name]

debug(f'vgchange volume group: {" ".join(cmd)}')
SysCommand(cmd)


def lvm_export_vg(vg: LvmVolumeGroup) -> None:
cmd = f'vgexport {vg.name}'

Expand Down
166 changes: 140 additions & 26 deletions archinstall/lib/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import textwrap
import time
from collections.abc import Callable
from contextlib import suppress
from pathlib import Path
from subprocess import CalledProcessError
from types import TracebackType
Expand All @@ -16,14 +17,16 @@
from archinstall.lib.command import SysCommand, run
from archinstall.lib.disk.fido import Fido2
from archinstall.lib.disk.luks import Luks2, unlock_luks2_dev
from archinstall.lib.disk.lvm import lvm_import_vg, lvm_pvseg_info, lvm_vol_change
from archinstall.lib.disk.lvm import lvm_import_vg, lvm_pvseg_info, lvm_vg_change, lvm_vol_change
from archinstall.lib.disk.utils import (
get_lsblk_by_mountpoint,
get_lsblk_info,
get_parent_device_path,
get_unique_path_for_device,
mount,
swapon,
udev_sync,
umount,
)
from archinstall.lib.exceptions import DiskError, HardwareIncompatibilityError, RequirementError, ServiceException, SysCallError
from archinstall.lib.hardware import SysInfo
Expand Down Expand Up @@ -131,45 +134,51 @@ def __init__(

self._zram_enabled = False
self._disable_fstrim = False

self._layout_teardown_required = False
self.pacman = Pacman(self.target, silent)

def __enter__(self) -> Self:
return self

def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None:
if exc_type is not None:
error(str(exc_value))
try:
if exc_type is not None:
error(str(exc_value))

self.sync_log_to_install_medium()
self.sync_log_to_install_medium()

# We avoid printing /mnt/<log path> because that might confuse people if they note it down
# and then reboot, and an identical log file will be found in the ISO medium anyway.
print(tr('[!] A log file has been created here: {}').format(logger.path))
print(tr('Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues'))
# We avoid printing /mnt/<log path> because that might confuse people if they note it down
# and then reboot, and an identical log file will be found in the ISO medium anyway.
print(tr('[!] A log file has been created here: {}').format(logger.path))
print(tr('Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues'))

# Return None to propagate the exception
return None
# Return None to propagate the exception
return None

info(tr('Syncing the system...'))
os.sync()
info(tr('Syncing the system...'))
os.sync()

if not (missing_steps := self.post_install_check()):
msg = f'Installation completed without any errors.\nLog files temporarily available at {logger.directory}.\nYou may reboot when ready.\n'
log(msg, fg='green')
self.sync_log_to_install_medium()
return True
else:
warn('Some required steps were not successfully installed/configured before leaving the installer:')
if not (missing_steps := self.post_install_check()):
msg = f'Installation completed without any errors.\nLog files temporarily available at {logger.directory}.\nYou may reboot when ready.\n'
log(msg, fg='green')
self.sync_log_to_install_medium()
return True
else:
warn('Some required steps were not successfully installed/configured before leaving the installer:')

for step in missing_steps:
warn(f' - {step}')
for step in missing_steps:
warn(f' - {step}')

warn(f'Detailed error logs can be found at: {logger.directory}')
warn('Submit this zip file as an issue to https://github.com/archlinux/archinstall/issues')
warn(f'Detailed error logs can be found at: {logger.directory}')
warn('Submit this zip file as an issue to https://github.com/archlinux/archinstall/issues')

self.sync_log_to_install_medium()
return False
self.sync_log_to_install_medium()
return False
finally:
try:
self.teardown()
except Exception as err:
warn(f'Failed to teardown installation target: {err}')

def remove_mod(self, mod: str) -> None:
if mod in self._modules:
Expand Down Expand Up @@ -257,6 +266,7 @@ def sanity_check(

def mount_ordered_layout(self) -> None:
debug('Mounting ordered layout')
self._layout_teardown_required = True

luks_handlers: dict[Any, Luks2] = {}

Expand Down Expand Up @@ -438,6 +448,110 @@ def _mount_btrfs_subvol(
options = mount_options + [f'subvol={subvol.name}']
mount(dev_path, mountpoint, options=options)

def teardown(self) -> None:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved out of the installer, preferably somewhere into the disk module, maybe the utils.py

if not self._layout_teardown_required:
debug('No mounted layout registered for teardown')
return

info('Tearing down installation target')

with suppress(Exception):
os.sync()

self._swapoff_layout()

with suppress(SysCallError, DiskError):
umount(self.target, recursive=True)

match self._disk_encryption.encryption_type:
case EncryptionType.LUKS:
self._teardown_luks_partitions()

case EncryptionType.LUKS_ON_LVM:
self._teardown_luks_lvm()
self._teardown_lvm()

case EncryptionType.LVM_ON_LUKS:
self._teardown_lvm()
self._teardown_luks_partitions()

case EncryptionType.NO_ENCRYPTION:
self._teardown_lvm()

with suppress(SysCallError):
udev_sync()

self._layout_teardown_required = False

def _swapoff_path(self, path: Path | None) -> None:
if path is None:
return

with suppress(SysCallError, DiskError):
SysCommand(['swapoff', str(path)])

def _swapoff_layout(self) -> None:
for mod in self._disk_config.device_modifications:
for part in mod.partitions:
if part.is_swap():
self._swapoff_path(part.dev_path)

if part.mapper_name:
self._swapoff_path(Path('/dev/mapper') / part.mapper_name)

if not self._disk_config.lvm_config:
return

for vol in self._disk_config.lvm_config.get_all_volumes():
if vol.fs_type == FilesystemType.LINUX_SWAP:
self._swapoff_path(vol.dev_path)

if vol.mapper_name:
self._swapoff_path(Path('/dev/mapper') / vol.mapper_name)

def _teardown_lvm(self) -> None:
lvm_config = self._disk_config.lvm_config

if not lvm_config:
return

for vg in reversed(lvm_config.vol_groups):
for vol in reversed(vg.volumes):
if not vol.dev_path:
continue

with suppress(SysCallError, DiskError):
lvm_vol_change(vol, False)

with suppress(SysCallError, DiskError):
lvm_vg_change(vg, False)

def _teardown_luks_partitions(self) -> None:
for part_mod in reversed(self._disk_encryption.partitions):
if not part_mod.dev_path or not part_mod.mapper_name:
continue

luks_handler = Luks2(
part_mod.dev_path,
mapper_name=part_mod.mapper_name,
)

with suppress(SysCallError, DiskError):
luks_handler.lock()

def _teardown_luks_lvm(self) -> None:
for vol in reversed(self._disk_encryption.lvm_volumes):
if not vol.dev_path or not vol.mapper_name:
continue

luks_handler = Luks2(
vol.dev_path,
mapper_name=vol.mapper_name,
)

with suppress(SysCallError, DiskError):
luks_handler.lock()

def generate_key_files(self) -> None:
match self._disk_encryption.encryption_type:
case EncryptionType.LUKS:
Expand Down