Skip to content
Draft
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
124 changes: 123 additions & 1 deletion archinstall/lib/disk/utils.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import os
from contextlib import suppress
from pathlib import Path
from typing import TYPE_CHECKING

from pydantic import BaseModel

from archinstall.lib.command import SysCommand
from archinstall.lib.exceptions import DiskError, SysCallError
from archinstall.lib.models.device import LsblkInfo
from archinstall.lib.models.device import EncryptionType, FilesystemType, LsblkInfo
from archinstall.lib.output import debug, info, warn

if TYPE_CHECKING:
from archinstall.lib.installer import Installer


class LsblkOutput(BaseModel):
blockdevices: list[LsblkInfo]
Expand Down Expand Up @@ -196,3 +202,119 @@ def swapon(path: Path) -> None:
SysCommand(['swapon', str(path)])
except SysCallError as err:
raise DiskError(f'Could not enable swap {path}:\n{err.message}')


def teardown(installer: Installer) -> None:
if not installer._layout_teardown_required:
debug('No mounted layout registered for teardown')
return

info('Tearing down installation target')

with suppress(Exception):
os.sync()

_swapoff_layout(installer)

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

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

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

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

case EncryptionType.NO_ENCRYPTION:
_teardown_lvm(installer)

with suppress(SysCallError):
udev_sync()

installer._layout_teardown_required = False


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

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


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

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

if not installer._disk_config.lvm_config:
return

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

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


def _teardown_lvm(installer: Installer) -> None:
from archinstall.lib.disk.lvm import lvm_vg_change, lvm_vol_change

lvm_config = installer._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(installer: Installer) -> None:
from archinstall.lib.disk.luks import Luks2

for part_mod in reversed(installer._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(installer: Installer) -> None:
from archinstall.lib.disk.luks import Luks2

for vol in reversed(installer._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()
58 changes: 33 additions & 25 deletions archinstall/lib/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
get_unique_path_for_device,
mount,
swapon,
teardown,
)
from archinstall.lib.exceptions import DiskError, HardwareIncompatibilityError, RequirementError, ServiceException, SysCallError
from archinstall.lib.hardware import SysInfo
Expand Down Expand Up @@ -131,45 +132,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:
teardown(self)
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 +264,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