diff --git a/Cargo.lock b/Cargo.lock
index afbf1b1b..12e8a560 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -782,6 +782,16 @@ dependencies = [
"rustc_version",
]
+[[package]]
+name = "file-guard"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21ef72acf95ec3d7dbf61275be556299490a245f017cf084bd23b4f68cf9407c"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
[[package]]
name = "filesystem-table"
version = "0.1.2"
@@ -2618,6 +2628,7 @@ dependencies = [
"const_format",
"derivative",
"enum_dispatch",
+ "file-guard",
"filesystem-table",
"format-bytes",
"freedesktop-desktop-entry",
diff --git a/Cargo.toml b/Cargo.toml
index 8c6851f7..b0199b02 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -68,6 +68,7 @@ no_color = "0.2.0"
poly_l10n = "0.0.6"
derivative = "2.2.0"
parking_lot = "0.12.3"
+file-guard = "0.2.0"
# [dependencies.os-detect]
# git = "https://github.com/FyraLabs/distinst"
diff --git a/po/en-US/readymade.ftl b/po/en-US/readymade.ftl
index 0364e40d..d0bb864a 100644
--- a/po/en-US/readymade.ftl
+++ b/po/en-US/readymade.ftl
@@ -92,4 +92,5 @@ stage-grub1 = Generating stage 1 grub.cfg in ESP...
stage-grub2 = Generating stage 2 grub.cfg in /boot/grub2/grub.cfg...
stage-biosgrub = Installing BIOS Grub2
stage-kernel = Reinstalling kernels
+stage-recovery = Setting up recovery environment
stage-selinux = Setting SELinux labels
diff --git a/po/es/readymade.ftl b/po/es/readymade.ftl
index 977eecfd..0c1d31aa 100644
--- a/po/es/readymade.ftl
+++ b/po/es/readymade.ftl
@@ -20,4 +20,10 @@ page-installation = Instalación
page-installation-progress = Instalando sistema base...
page-installationtype = Tipo de Instalación
page-installationtype-chromebook = Chromebook
-
+unknown-os = SO Desconocido
+page-installationtype-tpm = Habilitar TPM
+parttype-esp = Partición de sistema EFI ({ $path })
+parttype-home = Información de usuario
+parttype-other = Punto de montaje personalizado
+page-welcome = Bienvenido a { $distro }
+page-installationtype-encrypt = Habilitar encriptación de disco
diff --git a/po/fr/readymade.ftl b/po/fr/readymade.ftl
index 0de2ae87..ee4e9be3 100644
--- a/po/fr/readymade.ftl
+++ b/po/fr/readymade.ftl
@@ -5,7 +5,7 @@
next = Suivant
unknown-os = OS inconnu
parttype-root = Racine du système de fichiers ({ $path })
-parttype-esp = Partition EFI système ({ $path })
+parttype-esp = Partition système EFI ({ $path })
parttype-home = Données utilisateur ({ $path })
parttype-var = Données variables ({ $path })
page-welcome = Bienvenue dans { $distro }
@@ -45,7 +45,7 @@ page-installationtype-chromebook = Chromebook
page-installationtype-custom = Personnalisée
dialog-installtype-encrypt = Chiffrement du disque
dialog-installtype-password = Mot de passe
-dialog-installtype-repeat = Saisissez le mot de passe à nouveau
+dialog-installtype-repeat = Saisissez à nouveau le mot de passe
dialog-installtype-cancel = Annuler
dialog-installtype-confirm = Confirmer
installtype-edit-mp = Modifier le point de montage
@@ -53,7 +53,7 @@ installtype-rm-mp = Supprimer le point de montage
dialog-mp-part = Partition
dialog-mp-at = Monter sur
dialog-mp-opts = Options de montage
-installtype-parttool = Sélectionnez votre outil de partitionnement
+installtype-parttool = Sélectionner votre outil de partitionnement
stage-extracting = Extraction des fichiers
stage-copying = Copie des fichiers
stage-initramfs = Régénération de l'initramfs
@@ -73,9 +73,9 @@ page-installationtype-dual = Double démarrage
stage-grub2 = Génération du fichier grub.cfg d'étape 2 dans /boot/grub2/grub.cfg...
dialog-installtype-encrypt-desc =
Veuillez définir le mot de passe de chiffrement du disque.
- Si vous perdez ce mot de passe, vos données ne seront pas récupérables.
+ Si vous perdez ce mot de passe, vos données ne pourront pas être récupérées.
stage-grub = Génération des valeurs système par défaut pour GRUB
-stage-selinux = Définition des étiquettes SELinux
+stage-selinux = Ajout des étiquettes SELinux
page-confirmation-problem-device-mounted = { $dev } est monté sur { $mountpoint }. Démontez-le pour continuer.
page-confirmation-problem-devblkopen =
Le périphérique de blocs { $dev } est utilisé par les processus suivants :
diff --git a/po/ru/readymade.ftl b/po/ru/readymade.ftl
index 98b016e3..47f953e8 100644
--- a/po/ru/readymade.ftl
+++ b/po/ru/readymade.ftl
@@ -8,7 +8,7 @@ parttype-home = Домашняя директория пользователя (
parttype-var = Переменные данные ({ $path })
parttype-other = Пользовательская точка монтирования раздела
page-welcome = Добро пожаловать в { $distro }
-page-welcome-desc = Либо попробуйте { $distro } из этой программы установки, либо начните установку сейчас. Вы всегда можете вернуться к этому экрану, выбрав в меню приложение «Installer».
+page-welcome-desc = Вы можете попробовать { $distro }, или начать установку уже сейчас.
page-welcome-try = Попробовать
page-welcome-install = Установить
page-failure = Сбой установки
@@ -35,11 +35,11 @@ page-installation-help = Нужна помощь?
page-installation-help-desc = Задайте вопрос в одном из наших чатов!
page-installation-contrib = Внести вклад в { $distro }
page-installation-contrib-desc = Узнайте, как пожертвовать своим временем, деньгами или оборудованием.
-page-installation-progress = Установка базовой системы...
+page-installation-progress = Установка базовых системных пакетов...
page-installcustom = Пользовательская установка
page-installcustom-title = Разделы и точки монтирования
page-installcustom-desc = { $num } правил(а)
-page-installcustom-tool = Открыть дисковую утилиту
+page-installcustom-tool = Открыть утилиту разметки
page-installcustom-add = Добавить новое правило
page-installationtype = Тип установки
page-installationtype-entire = Весь диск
@@ -61,7 +61,7 @@ installtype-rm-mp = Удалить точку монтирования
dialog-mp-part = Раздел
dialog-mp-at = Монтировать на
dialog-mp-opts = Настройки монтирования
-installtype-parttool = Выберите вашу дисковую утилиту
+installtype-parttool = Выберите свою утилиту разметки
stage-extracting = Распаковка файлов
stage-copying = Копирование файлов
stage-mkpart = Создание разделов и копирование файлов
@@ -70,5 +70,10 @@ stage-grub = Генерация системных настроек grub по у
stage-grub1 = Генерация stage 1 grub.cfg на ESP...
stage-grub2 = Генерация stage 2 grub.cfg в /boot/grub2/grub.cfg...
stage-biosgrub = Установка BIOS Grub2
-stage-kernel = Повторная установка ядер
+stage-kernel = Переустановка ядер
stage-selinux = Установка политик SELinux
+page-confirmation-problem-devblkopen =
+ Накопитель { $dev } уже используется следующими процессами:
+ { $pids }
+ Данные процессы должны быть остановлены для дальнейшей работы установщика.
+page-confirmation-problem-device-mounted = { $dev } уже смонтирован в { $mountpoint }. Размонтируйте чтобы продолжить.
diff --git a/src/backend/install.rs b/src/backend/install.rs
index f0200060..0ccb68a9 100644
--- a/src/backend/install.rs
+++ b/src/backend/install.rs
@@ -1,8 +1,10 @@
+use file_guard::Lock;
use ipc_channel::ipc::{IpcError, IpcOneShotServer, IpcSender};
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use std::{
io::Write,
+ os::unix::process::CommandExt,
path::{Path, PathBuf},
process::{Command, Stdio},
sync::OnceLock,
@@ -818,17 +820,29 @@ impl FinalInstallationState {
["--key-file", keyfile_path]
});
- let repart_cmd = Command::new("systemd-repart")
- .args(["--dry-run", if dry_run { "yes" } else { "no" }])
- .args(["--definitions", cfgdir.to_str().unwrap()])
- .args(["--empty", "force", "--offline", "false", "--json", "pretty"])
- .args(["--copy-source", ©_source].iter().filter(|_| !is_bootc))
- .args(arg_keyfile.iter().flatten())
- .arg(blockdev)
- .stdout(Stdio::piped())
- .stderr(Stdio::inherit())
- .output()
- .context("can't run systemd-repart")?;
+ // Scope to ensure device and lock live long enough for the command
+ let repart_cmd = {
+ // lock device
+ let mut device = std::fs::OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(blockdev)
+ .context("Failed to open block device")?;
+ // We are locking the device so that repart doesn't fail due to device busy
+ // The lock is held in this scope
+ let mut _lock = file_guard::lock(&mut device, Lock::Exclusive, 0, 1)?;
+ Command::new("systemd-repart")
+ .args(["--dry-run", if dry_run { "yes" } else { "no" }])
+ .args(["--definitions", cfgdir.to_str().unwrap()])
+ .args(["--empty", "force", "--offline", "false", "--json", "pretty"])
+ .args(["--copy-source", ©_source].iter().filter(|_| !is_bootc))
+ .args(arg_keyfile.iter().flatten())
+ .arg(blockdev)
+ .stdout(Stdio::piped())
+ .stderr(Stdio::inherit())
+ .output()
+ .context("can't run systemd-repart")?
+ };
if !repart_cmd.status.success() {
bail!(
diff --git a/src/backend/postinstall/reinstall_kernel.rs b/src/backend/postinstall/reinstall_kernel.rs
index 54c9783f..ca69a089 100644
--- a/src/backend/postinstall/reinstall_kernel.rs
+++ b/src/backend/postinstall/reinstall_kernel.rs
@@ -11,21 +11,29 @@ pub struct ReinstallKernel;
impl PostInstallModule for ReinstallKernel {
fn run(&self, _context: &Context) -> Result<()> {
let kernel_vers = std::fs::read_dir("/lib/modules")?
- .map(|entry| entry.unwrap().file_name())
+ .filter_map(|entry| entry.ok().map(|e| e.file_name()))
.collect_vec();
tracing::info!(?kernel_vers, "Kernel versions found");
// We're gonna just install the first kernel we find, so let's do that
- let kver = kernel_vers.first().unwrap().to_str().unwrap();
+ let kver = kernel_vers
+ .first()
+ .ok_or_else(|| color_eyre::eyre::eyre!("No kernel versions found in /lib/modules"))?
+ .to_str()
+ .ok_or_else(|| color_eyre::eyre::eyre!("Kernel version filename is not valid UTF-8"))?;
// install kernel
+ let vmlinuz_path = format!("/lib/modules/{kver}/vmlinuz");
+ if !std::path::Path::new(&vmlinuz_path).exists() {
+ bail!("Kernel version {kver} does not have a vmlinuz file at {vmlinuz_path}");
+ }
stage!(kernel {
let kernel_install_cmd_status = Command::new("kernel-install")
.arg("add")
.arg(kver)
- .arg(format!("/lib/modules/{kver}/vmlinuz"))
+ .arg(&vmlinuz_path)
.arg("--verbose")
.status()?;
@@ -37,6 +45,39 @@ impl PostInstallModule for ReinstallKernel {
}
});
+ stage!(recovery {
+ // copy to /boot/vmlinuz-recovery
+ let recovery_vmlinuz_path = "/boot/vmlinuz-recovery".to_owned();
+ if std::path::Path::new(&recovery_vmlinuz_path).exists() {
+ tracing::warn!("Recovery kernel already exists at {recovery_vmlinuz_path}, skipping copy");
+ } else {
+ std::fs::copy(&vmlinuz_path, &recovery_vmlinuz_path)
+ .map_err(|e| color_eyre::eyre::eyre!(e))?;
+ }
+
+ // create a recovery initramfs
+ let recovery_initramfs_path = "/boot/initramfs-recovery.img".to_owned();
+
+ let recovery_dracut = Command::new("dracut")
+ .arg("--force")
+ .arg("--add")
+ .arg("dmsquash-live overlayfs rescue")
+ .arg("--no-hostonly")
+ .arg("--no-uefi")
+ .arg("--kver")
+ .arg(kver)
+ .arg(&recovery_initramfs_path)
+ .status()?;
+ if !recovery_dracut.success() {
+ bail!(
+ "dracut failed with exit code {:?}",
+ recovery_dracut.code()
+ );
+ }
+ tracing::info!("Recovery initramfs created at {recovery_initramfs_path}");
+ });
+ // todo: grub.d template for boot entry in OS
+
Ok(())
}
}
diff --git a/src/backend/repart_output.rs b/src/backend/repart_output.rs
index 772310ce..3d68b463 100644
--- a/src/backend/repart_output.rs
+++ b/src/backend/repart_output.rs
@@ -229,6 +229,13 @@ impl RepartOutput {
.map(|part| part.node.clone())
}
+ pub fn get_recovery_partition(&self) -> std::option::Option {
+ self.partitions
+ .iter()
+ .find(|part| part.label == "os_recovery")
+ .map(|part| part.node.clone())
+ }
+
/// Create [`tiffin::Container`] from the repartitioning output with the mountpoints
/// from the DDI partition types
pub fn to_container(
diff --git a/templates/wholedisk/20-efi.conf b/templates/wholedisk/20-efi.conf
index b5dae29d..13cbb0d5 100644
--- a/templates/wholedisk/20-efi.conf
+++ b/templates/wholedisk/20-efi.conf
@@ -6,3 +6,4 @@ SizeMaxBytes=512M
MountPoint=/boot/efi:umask=0077,shortname=winnt
# This path is actually relative; see man repart.d at --copy-source option
CopyFiles=/boot/efi/:/
+FactoryReset=true
\ No newline at end of file
diff --git a/templates/wholedisk/45-boot.conf b/templates/wholedisk/45-boot.conf
index 82109b16..145fee73 100644
--- a/templates/wholedisk/45-boot.conf
+++ b/templates/wholedisk/45-boot.conf
@@ -1,10 +1,11 @@
[Partition]
Format=ext4
SizeMinBytes=1G
-Weight=100
+Weight=200
SizeMaxBytes=2G
MountPoint=/boot/:defaults
# This path is actually relative; see man repart.d at --copy-source option
CopyFiles=/boot:/
ExcludeFiles=/boot/efi
Type=xbootldr
+FactoryReset=true
\ No newline at end of file
diff --git a/templates/wholedisk/47-recovery.conf b/templates/wholedisk/47-recovery.conf
new file mode 100644
index 00000000..c10187da
--- /dev/null
+++ b/templates/wholedisk/47-recovery.conf
@@ -0,0 +1,16 @@
+# This partition is an EROFS mirror of rootfs, used for special recovery purposes
+[Partition]
+# Let's reserve this
+Type=8DA63339-0007-60C0-C436-083AC8230908
+Format=erofs
+SizeMinBytes=4G
+CopyFiles=/:/
+Label=os_recovery
+# Priority of 5000 ensures that it wouldn't be created if
+# We don't have enough space
+Priority=5000
+Weight=100
+# This partition will be 4-8GB in size, for future-proofing and
+# recovery image updates.
+SizeMaxBytes=8G
+FactoryReset=false
\ No newline at end of file
diff --git a/templates/wholedisk/50-root.conf b/templates/wholedisk/50-root.conf
index b4ec0682..17dd3f61 100644
--- a/templates/wholedisk/50-root.conf
+++ b/templates/wholedisk/50-root.conf
@@ -5,7 +5,10 @@ DefaultSubvolume=/
Type=root
Compression=zstd
ExcludeFiles=/boot/
+Priority=-10000
+Weight=5000
Format=btrfs
CopyFiles=/:/
Subvolumes=/ /home
CompressionLevel=1
+FactoryReset=true
\ No newline at end of file