Add installer teardown for LUKS and LVM layouts

This commit is contained in:
Guilherme Sales 2026-04-28 17:23:40 +01:00
parent c4d68855cb
commit ea64876595
2 changed files with 148 additions and 26 deletions

View File

@ -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}'

View File

@ -6,6 +6,7 @@ import subprocess
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
@ -16,7 +17,7 @@ from archinstall.lib.bootloader.utils import validate_bootloader_layout
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,
@ -24,6 +25,8 @@ from archinstall.lib.disk.utils import (
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
@ -131,45 +134,51 @@ class Installer:
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:
@ -257,6 +266,7 @@ class Installer:
def mount_ordered_layout(self) -> None:
debug('Mounting ordered layout')
self._layout_teardown_required = True
luks_handlers: dict[Any, Luks2] = {}
@ -438,6 +448,110 @@ class Installer:
options = mount_options + [f'subvol={subvol.name}']
mount(dev_path, mountpoint, options=options)
def teardown(self) -> None:
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: