forked from pisilinux-rs/yali-rs
fix(P0): parola sızıntısı, parted birim hatası, hatalı LUKS cmdline, yutulan hatalar, hata temizliği
Veri kaybı / güvenlik / açılmayan sistem düzeyindeki 7 sorun giderildi.
## 1. Parola düz metin olarak argv'ye geçiyordu (jobs/users.rs)
`sh -c "echo 'user:pass' | chpasswd -c SHA512"` deseni kaldırıldı:
- parola /proc/<pid>/cmdline üzerinden okunabiliyordu
- ' veya $(...) içeren parola komutu bozuyordu
Doğru desen (`set_password_via_chpasswd`, stdin pipe) geri getirildi —
jobs/partition.rs::run_luks_format ile aynı. Yazılan veri yazımdan
sonra sıfırlanıyor. Dosyadaki yorum satırındaki güvenli sürüm de
silinerek tek uygulama bırakıldı.
## 2. parted "MB" birim tuzağı geri gelmişti (jobs/partition.rs:237)
EFI ve swap `{}MiB` geçerken kök bölüm `{}MB` idi. parted "MB"yi ondalık
(10^6) okur, bizim hesaplar ikilidir (2^20): 4609MB = 9001992.1875
sektör → hizasız başlangıç. AGENTS.md bunu KRİTİK not almıştı;
`7209d58` ile düzeltilmiş, sonra regresyona uğramıştı. `{}MiB` yapıldı
ve nedeni yorumla sabitlendi.
## 3. LUKS çekirdek parametresi geçersizdi (jobs/bootloader.rs)
`cryptdevice=UUID=<x>:cryptroot` geçerli bir söz dizimi değil, üstelik
kök bölümü `is_uefi` ile tahmin ediliyordu (UEFI'de 3, BIOS'ta 2) —
manuel modda bu numaralar assign_partition_numbers'tan geldiği için
ilgisiz bir bölümün UUID'si okunabiliyordu. `.unwrap_or_default()`
`UUID=:` üretebiliyordu.
Artık `cryptsetup status` ile mapper'ın arkasındaki GERÇEK aygıt
çalışma zamanında çözülüyor, blkid ile LUKS başlık UUID'si alınıyor,
ve dracut (`rd.luks.uuid=luks-…`) + mkinitcpio (`cryptdevice=…`)
biçimleri birlikte yazılıyor. UUID boş çıkarsa hata verilir; tahmin
yok.
Ayrıca `CRYPTROOT_NAME` sabiti tanımlandı: "cryptroot" 5 yerde
hardcoded'dı, artık tek kaynaktan geliyor.
## 4. Kritik hatalar yutuluyordu (jobs/finish.rs)
- RunComarJob tamamen yorum satırıydı → `pisi configure-pending`
hiç çalışmıyordu, paketlerin post-install betikleri koşmuyordu.
Geri getirildi; "yapılandırılacak paket yok" durumu zararsız sayılıyor.
- ldconfig ve update-environment hataları loglanıp yutuluyordu.
update-environment PisiLinux init finalizasyonudur: yutulursa
kurulum "başarılı" görünür ama sistem açılmaz. Artık fatal.
- CleanupLiveJob'da 10 adet `let _ =`; depo tanımları sessizce
başarısız olabiliyordu (kullanıcı paket kuramaz). Artık fatal.
## 5. Hata durumunda hiç temizlik yapılmıyordu (jobs/mod.rs)
run_all ilk hatada return ediyordu: hedef bölümler bağlı, LUKS
mapper'ları açık, swap aktif kalıyordu. Yeniden başlatma reddediliyor,
elle düzeltmesi dakikalar sürüyordu.
- JobQueue::cleanup listesi eklendi; with_cleanup(...) ile verilen
işler başarıda da hatada da çalışır.
- Yeni EmergencyCleanupJob (jobs/cleanup.rs): tüm mount'lar → LVM
lvchange -an → cryptsetup close (yalnızca hedef diskin
mapper'ları) → swapoff.
- Temizlik de hata verirse bu hatalar asıl hataya eklenip
kullanıcıya "elle ne yapılacak" listesi olarak gösterilir.
## 6. UnmountBindJob /home, /var, /boot'u çözmüyordu (jobs/execution.rs)
Elle yazılmış sabit liste (proc, sys, dev, boot/efi) yalnızca otomatik
modda doğruydu; manuel düzende ek bölümler kurulum bitince bağlı
kalıyordu. Artık /proc/self/mountinfo okunuyor ve mount/ altındaki her
şey en derinden yüzeye çözülüyor — düzen bilinmeye gerek yok.
mountinfo'daki \0NNN oktal kaçışlarını çözen unescape_mount_field ve
mounted_paths_under eklendi.
## 7. Parolasız LUKS kombinasyonu (jobs/partition.rs)
encrypt_root=true + boş parola → kök düz ext4 formatlanıyor, sonra
MountPartitionsJob /dev/mapper/cryptroot'u mount etmeye çalışıp
kurulumu öldürüyordu. Job başında reddediliyor.
## 8. rescue.rs'te iki panik noktası
- &entry.summary[..50]: len() bayt sayar, dilim bayt sınırı ister.
pisi history çıktısı paket adlarından gelir ve Türkçe karakter
içerir (ş/ğ 2 bayt) → sınırda PANİK. chars().take(50) ile değişti.
- parse_pisi_history: '[' ve ']' bağımsız aranıyordu; ']' önce
gelen girdide remainder[start+1..end] PANİK veriyordu. Kapanış
parantez açılıştan sonra aranıyor.
Her ikisi için 5 test eklendi (rescue.rs'te test modu yoktu).
## Doğrulama
cargo check --all-targets : 0 hata, 0 uyarı
cargo test : 28/28 geçti (23 + 5 yeni)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+75
-5
@@ -1,6 +1,52 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
use super::{blkid_uuid, DEMO_MODE, get_fs_type, is_mount_point, part_path, run_cmd, run_chroot, Job, UiSender};
|
||||
|
||||
/// Kök bölümün LUKS mapper adı. `PartitionJob`, `MountPartitionsJob`,
|
||||
/// `GenerateFstabJob` ve `InstallGrubJob` aynı adı kullanır; tek
|
||||
/// kaynaktan okunsun diye burada tanımlı.
|
||||
pub const CRYPTROOT_NAME: &str = "cryptroot";
|
||||
|
||||
/// Açık bir LUKS mapper'ının arkasındaki GERÇEK aygıtı döndürür
|
||||
/// (`/dev/sda3` gibi).
|
||||
///
|
||||
/// Neden gerekli: `blkid /dev/mapper/cryptroot` dosya sisteminin UUID'sini
|
||||
/// verir, LUKS başlık UUID'sini değil. Kernel'e `rd.luks.uuid=` ile
|
||||
/// verilmesi gereken değer LUKS başlığınkidir; bu yüzden önce mapper'ın
|
||||
/// altındaki bölüme inmek gerekir. `cryptsetup status` tam olarak bu
|
||||
/// bilgiyi verir ve çalışma zamanında doğrudur — bölüm numarası
|
||||
/// tahmin etmeye gerek kalmaz (manuel modda numaralar `is_uefi`'ye göre
|
||||
/// değil `assign_partition_numbers`'a göre belirlenir).
|
||||
async fn luks_backing_device(name: &str) -> Result<String, String> {
|
||||
if DEMO_MODE.load(Ordering::Relaxed) {
|
||||
return Ok("/dev/demo-luks".to_string());
|
||||
}
|
||||
|
||||
let out = tokio::process::Command::new("cryptsetup")
|
||||
.args(["status", name])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("cryptsetup status {} çalıştırılamadı: {}", name, e))?;
|
||||
|
||||
// `cryptsetup status` stdout'a:
|
||||
// /dev/mapper/cryptroot is active and is in use.
|
||||
// type: LUKS2
|
||||
// device: /dev/sda3
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
let device = stdout
|
||||
.lines()
|
||||
.find_map(|l| l.trim().strip_prefix("device:"))
|
||||
.map(|d| d.trim().to_string())
|
||||
.filter(|d| !d.is_empty());
|
||||
|
||||
device.ok_or_else(|| {
|
||||
format!(
|
||||
"{} mapper'ının altındaki aygıt çözümlenemedi. \
|
||||
LUKS başlığı açılmamış olabilir; fstab/crypttab kontrol edin.",
|
||||
name
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// initramfs yeniler (GRUB öncesi zorunlu).
|
||||
/// PisiLinux `dracut` kullanıyorsa `dracut -f --regenerate-all`,
|
||||
/// mkinitcpio kullanıyorsa `mkinitcpio -P`.
|
||||
@@ -180,11 +226,35 @@ impl Job for InstallGrubJob {
|
||||
// kernel cmdline
|
||||
let mut cmdline = self.kernel_options.clone();
|
||||
if self.encrypt_root {
|
||||
// Kök bölüm UUID'sini blkid'den al (LUKS başlık UUID'si)
|
||||
let (root_num, _swap_num): (u32, u32) = if self.is_uefi { (3, 2) } else { (2, 1) };
|
||||
let root_part = part_path(&self.disk, root_num);
|
||||
let root_uuid = blkid_uuid(&root_part).await.unwrap_or_default();
|
||||
let crypt_arg = format!("cryptdevice=UUID={}:cryptroot root=/dev/mapper/cryptroot", root_uuid);
|
||||
// LUKS başlık UUID'si GEREKLİ. Önceden bölüm numarası tahmin
|
||||
// ediliyordu (UEFI'de 3, BIOS'ta 2) — bu manuel modda YANLIŞTIR
|
||||
// (numaralar assign_partition_numbers'tan gelir) ve üstelik
|
||||
// "cryptdevice=UUID=x:cryptroot" geçerli bir söz dizimi değildir.
|
||||
//
|
||||
// Bunun yerine çalışma zamanında gerçeği soruyoruz:
|
||||
// `cryptsetup status` LUKS başlığının bulunduğu aygıtı verir,
|
||||
// blkid ise o aygıtın LUKS UUID'sini (crypto_LUKS).
|
||||
// /dev/mapper/cryptroot'un UUID'si değil — mapper'ın arkasındaki
|
||||
// bölümünki kullanılır.
|
||||
let luks_part = luks_backing_device(CRYPTROOT_NAME).await?;
|
||||
let luks_uuid = blkid_uuid(&luks_part).await
|
||||
.map_err(|e| format!("{} LUKS UUID okunamadı: {}", luks_part, e))?;
|
||||
if luks_uuid.trim().is_empty() {
|
||||
return Err(format!("{} için LUKS UUID boş geldi", luks_part));
|
||||
}
|
||||
|
||||
ui.log(format!("LUKS: {} → {}", luks_part, luks_uuid));
|
||||
|
||||
// dracut sözdizimi: rd.luks.uuid=luks-<uuid>
|
||||
// mkinitcpio sözdizimi: cryptdevice=UUID=<uuid>:<mapper>
|
||||
// PisiLinux hangisini kullandığı belli olmadığı için ikisi de
|
||||
// yazılır; iki araç da diğerinin anahtarını yok sayar.
|
||||
let crypt_arg = format!(
|
||||
"rd.luks.uuid=luks-{uuid} rd.luks.name={uuid}={name} \
|
||||
root=/dev/mapper/{name} cryptdevice=UUID={uuid}:{name}",
|
||||
uuid = luks_uuid,
|
||||
name = CRYPTROOT_NAME,
|
||||
);
|
||||
if cmdline.is_empty() {
|
||||
cmdline = crypt_arg;
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
//! Kurulum bir hata ile durduğunda veya bittiğinde çalışan temizlik işleri.
|
||||
//!
|
||||
//! Bu modülün varlık nedeni: `JobQueue::run_all` eskiden ilk hatada
|
||||
//! `return` ediyordu. O an hedef bölümler bağlıydı, LUKS mapper'ları
|
||||
//! açıktı ve swap aktifti — kullanıcı yeniden başlatmaya çalışırsa
|
||||
//! sistem onu reddediyor, elle de düzeltmesi dakikalar sürüyordu.
|
||||
//!
|
||||
//! Temizlik işleri `JobQueue::with_cleanup(...)` ile verilir ve başarı
|
||||
//! durumunda da, hata durumunda da çalışır.
|
||||
|
||||
use super::execution::umount_clean;
|
||||
use super::{mounted_paths_under, Job, UiSender, DEMO_MODE};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
/// Kurulum bitince veya hata ile durunca hedef sistemin bağlamalarını
|
||||
/// tamamen çözer: tüm mount'lar, LUKS mapper'ları, LVM birimleri ve swap.
|
||||
///
|
||||
/// Sıralama önemlidir — en derinden yüzeye:
|
||||
/// 1. `mount` altındaki tüm dosya sistemleri
|
||||
/// 2. LVM mantıksal birimleri (`lvchange -an`)
|
||||
/// 3. LUKS mapper'ları (`cryptsetup close`)
|
||||
/// 4. Hedef diskin swap bölümü (`swapoff`)
|
||||
///
|
||||
/// Swap en sonda: swap'a yazmış bir süreç varsa erken swapoff denemesi
|
||||
/// "device is busy" verir.
|
||||
pub struct EmergencyCleanupJob {
|
||||
pub mount: String,
|
||||
/// Hedef diskin bölüm yolları (swapoff için). Boşsa otomatik bulunur.
|
||||
pub partitions: Vec<String>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Job for EmergencyCleanupJob {
|
||||
fn name(&self) -> &str { "job_cleanup_emergency" }
|
||||
|
||||
async fn run(&self, ui: &UiSender) -> Result<(), String> {
|
||||
if DEMO_MODE.load(Ordering::Relaxed) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut errors: Vec<String> = Vec::new();
|
||||
|
||||
// ── 1. Dosya sistemleri ────────────────────────────────
|
||||
let targets = mounted_paths_under(&self.mount);
|
||||
if targets.is_empty() {
|
||||
ui.log("Bağlı dosya sistemi yok");
|
||||
}
|
||||
for target in &targets {
|
||||
if !umount_clean(target, ui).await {
|
||||
errors.push(format!("{} bağlı kaldı", target));
|
||||
}
|
||||
}
|
||||
|
||||
// ── 2. LVM mantıksal birimleri ────────────────────────
|
||||
// Etkin VG'lerin tüm LV'leri kapatılır. `vgs --noheadings` boş
|
||||
// dönerse sistemde LVM yok demektir, hata değil.
|
||||
if let Err(e) = deactivate_lvm(ui).await {
|
||||
errors.push(e);
|
||||
}
|
||||
|
||||
// ── 3. LUKS mapper'ları ───────────────────────────────
|
||||
// `lsblk` TYPE=crypt listesi. Hedef diskin LUKS bölümü hangisi
|
||||
// ise onu kapatır; canlı ortamın kendi LUKS'u varsa dokunmaz.
|
||||
match close_crypt_mappers(&self.partitions).await {
|
||||
Ok(closed) => {
|
||||
for name in closed {
|
||||
ui.log(format!("LUKS kapatıldı: {}", name));
|
||||
}
|
||||
}
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
|
||||
// ── 4. Swap ───────────────────────────────────────────
|
||||
for part in &self.partitions {
|
||||
if let Err(e) = swapoff_part(part, ui).await {
|
||||
errors.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(errors.join("\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Etkin tüm LVM VG'lerini deaktive eder.
|
||||
async fn deactivate_lvm(ui: &UiSender) -> Result<(), String> {
|
||||
let out = tokio::process::Command::new("vgs")
|
||||
.args(["--noheadings", "-o", "vg_name"])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("vgs çalıştırılamadı: {}", e))?;
|
||||
|
||||
let vgs: Vec<String> = String::from_utf8_lossy(&out.stdout)
|
||||
.lines()
|
||||
.map(|l| l.trim().to_string())
|
||||
.filter(|l| !l.is_empty())
|
||||
.collect();
|
||||
|
||||
if vgs.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut failed = Vec::new();
|
||||
for vg in vgs {
|
||||
ui.log(format!("LVM deaktive ediliyor: {}", vg));
|
||||
match super::run_cmd("vgchange", &["-an", &vg]).await {
|
||||
Ok(()) => {}
|
||||
Err(e) => failed.push(format!("{} ({})", vg, e)),
|
||||
}
|
||||
}
|
||||
|
||||
if failed.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("LVM deaktive edilemedi: {}", failed.join(", ")))
|
||||
}
|
||||
}
|
||||
|
||||
/// Açık LUKS mapper'larını kapatır.
|
||||
///
|
||||
/// `partitions` verilmişse yalnızca o bölümlerin mapper'ları kapatılır
|
||||
/// (hedef diske ait olanlar). `cryptsetup luksUUID <dev>` ile eşleştirme
|
||||
/// yapılır. Liste boşsa hiçbir şeye dokunulmaz — canlı ortamın kendi
|
||||
/// şifreli bölümlerini kapatmak kullanıcının verisini riske atar.
|
||||
async fn close_crypt_mappers(partitions: &[String]) -> Result<Vec<String>, String> {
|
||||
if partitions.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mappers = super::open_crypt_mappers();
|
||||
if mappers.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut closed = Vec::new();
|
||||
for name in mappers {
|
||||
// Bu mapper hangi bölümün arkasında?
|
||||
let backing = backing_device(&name).await;
|
||||
let belongs = backing
|
||||
.as_deref()
|
||||
.map(|d| partitions.iter().any(|p| p == d))
|
||||
.unwrap_or(false);
|
||||
|
||||
if !belongs {
|
||||
continue;
|
||||
}
|
||||
|
||||
match super::run_cmd("cryptsetup", &["close", &name]).await {
|
||||
Ok(()) => closed.push(name),
|
||||
Err(e) => {
|
||||
return Err(format!(
|
||||
"cryptsetup close {} başarısız: {}. \
|
||||
Hedef bölüm hâlâ kullanımda olabilir.",
|
||||
name, e
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(closed)
|
||||
}
|
||||
|
||||
/// Bir LUKS mapper'ının arkasındaki gerçek aygıtı döndürür.
|
||||
async fn backing_device(mapper: &str) -> Option<String> {
|
||||
let out = tokio::process::Command::new("cryptsetup")
|
||||
.args(["status", mapper])
|
||||
.output()
|
||||
.await
|
||||
.ok()?;
|
||||
String::from_utf8_lossy(&out.stdout)
|
||||
.lines()
|
||||
.find_map(|l| l.trim().strip_prefix("device:"))
|
||||
.map(|d| d.trim().to_string())
|
||||
.filter(|d| !d.is_empty())
|
||||
}
|
||||
|
||||
/// Verilen bölüm swap ise `swapoff` çalıştırır. Swap değilse sessizce geçer.
|
||||
async fn swapoff_part(part: &str, ui: &UiSender) -> Result<(), String> {
|
||||
let out = tokio::process::Command::new("swapon")
|
||||
.args(["--show", "--noheadings", "-o", "NAME"])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("swapon sorgusu başarısız: {}", e))?;
|
||||
|
||||
let active: Vec<String> = String::from_utf8_lossy(&out.stdout)
|
||||
.lines()
|
||||
.map(|l| l.trim().to_string())
|
||||
.filter(|l| !l.is_empty())
|
||||
.collect();
|
||||
|
||||
if !active.iter().any(|s| s == part) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ui.log(format!("swapoff {}", part));
|
||||
super::run_cmd("swapoff", &[part]).await
|
||||
.map_err(|e| format!("swapoff {} başarısız: {} (bu bölümü kullanan süreçleri kapatın)", part, e))
|
||||
}
|
||||
+18
-22
@@ -326,33 +326,29 @@ pub struct UnmountBindJob {
|
||||
impl Job for UnmountBindJob {
|
||||
fn name(&self) -> &str { "job_umount_bind" }
|
||||
|
||||
/// `mount` altındaki TÜM bağlı dosya sistemlerini en derinden yüzeye
|
||||
/// doğru çözer.
|
||||
///
|
||||
/// Önceden elle yazılmış sabit bir liste vardı (`proc`, `sys`, `dev`,
|
||||
/// `boot/efi`, ...). Bu liste yalnızca otomatik modda doğruydu:
|
||||
/// manuel bölümlemede `/home`, `/var`, `/boot` gibi ek bölümler
|
||||
/// kurulum bitince bağlı kalıyordu. Artık düzen bilinmiyor —
|
||||
/// `/proc/self/mountinfo` okunup `mount/` altındaki her şey bulunuyor.
|
||||
async fn run(&self, ui: &UiSender) -> Result<(), String> {
|
||||
let mt = &self.mount;
|
||||
// Ters sırada umount — en derin path önce
|
||||
let subdirs = [
|
||||
"sys/firmware/efi/efivars",
|
||||
"dev/pts",
|
||||
"dev",
|
||||
"run",
|
||||
"sys",
|
||||
"proc",
|
||||
"boot/efi",
|
||||
];
|
||||
let mut errors: Vec<String> = Vec::new();
|
||||
|
||||
// Bind-mount'lar + ESP (/boot/efi)
|
||||
for subdir in &subdirs {
|
||||
let target = format!("{}/{}", mt, subdir);
|
||||
if !umount_clean(&target, ui).await {
|
||||
let msg = format!("{} temiz unmount edilemedi", target);
|
||||
errors.push(msg);
|
||||
}
|
||||
let mut targets = super::mounted_paths_under(mt);
|
||||
|
||||
if targets.is_empty() {
|
||||
ui.log("Çözülecek bağlama noktası yok (zaten temiz)");
|
||||
targets.push(mt.clone());
|
||||
}
|
||||
|
||||
// Kök bölüm: chroot mount'unun kendisi
|
||||
if !umount_clean(mt, ui).await {
|
||||
let msg = format!("{} (kök) temiz unmount edilemedi", mt);
|
||||
errors.push(msg);
|
||||
for target in &targets {
|
||||
if !umount_clean(target, ui).await {
|
||||
errors.push(format!("{} temiz unmount edilemedi", target));
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
@@ -368,7 +364,7 @@ impl Job for UnmountBindJob {
|
||||
/// Tek bir mount noktasını önce temiz `umount` ile, başarısızsa `umount -lf`
|
||||
/// (lazy) ile çözer. "not mounted" / "no such file" gibi beklenen durumlar
|
||||
/// kritik sayılmaz; yalnızca komut çalıştırılamaması gerçek hatadır.
|
||||
async fn umount_clean(target: &str, ui: &UiSender) -> bool {
|
||||
pub(crate) async fn umount_clean(target: &str, ui: &UiSender) -> bool {
|
||||
ui.log(format!("umount {}", target));
|
||||
match tokio::process::Command::new("umount")
|
||||
.arg(target).status().await
|
||||
|
||||
+69
-46
@@ -1,8 +1,9 @@
|
||||
use super::{run_chroot, Job, UiSender};
|
||||
|
||||
/// COMAR servislerini ve tetikleyicilerini yapılandırır.
|
||||
/// `pisi configure-pending` (kısaltması `pisi cp`): kurulan paketlerin
|
||||
/// post-install betiklerini çalıştırır. Bu adım olmadan kurulu sistemde
|
||||
/// servisler/tetikleyiciler kurulmamış olur ve sistem düzgün açılmaz.
|
||||
pub struct RunComarJob {
|
||||
#[allow(dead_code)]
|
||||
pub mount: String,
|
||||
}
|
||||
|
||||
@@ -11,27 +12,32 @@ impl Job for RunComarJob {
|
||||
fn name(&self) -> &str { "job_comar" }
|
||||
|
||||
async fn run(&self, ui: &UiSender) -> Result<(), String> {
|
||||
// pisi configure-pending: paket post-install scriptlerini çalıştırır.
|
||||
// Yapılandırılacak paket yoksa çıkış kodu sıfır dışı olabilir — fatal değil.
|
||||
// pisi cp işlemi kurulum sonrası atlanıyor (kullanıcı isteği).
|
||||
let _ = ui;
|
||||
// ui.log("pisi paketleri yapılandırılıyor (COMAR)…");
|
||||
// match run_chroot(&self.mount, &["pisi", "cp"]).await {
|
||||
// Ok(_) => {
|
||||
// ui.log("✓ pisi paketleri yapılandırıldı");
|
||||
// }
|
||||
// Err(e) => {
|
||||
// // "no packages to configure" gibi durumlar hata değil
|
||||
// if e.contains("no packages") || e.contains("Nothing to do") {
|
||||
// ui.log(" pisi: yapılandırılacak paket yok, atlanıyor");
|
||||
// } else {
|
||||
// // Gerçek hata — uyar ama kurulumu durdurma
|
||||
// ui.log(format!("pisi paketleri yapılandırma uyarısı: {}", e));
|
||||
// ui.log("Bu uyarı kritik değilse kurulum devam edecek.");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
Ok(())
|
||||
ui.log("pisi paketleri yapılandırılıyor (configure-pending)…");
|
||||
match run_chroot(&self.mount, &["pisi", "cp"]).await {
|
||||
Ok(_) => {
|
||||
ui.log("✓ pisi paketleri yapılandırıldı");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
// "Yapılandırılacak paket yok" bir hata değil; yeni kurulumda
|
||||
// (ör. yalnızca temel sistem) normal olarak oluşabilir.
|
||||
let benign = ["no packages to configure", "nothing to do",
|
||||
"yapılandırılacak paket", "nothing to configure"];
|
||||
if benign.iter().any(|m| e.to_lowercase().contains(m)) {
|
||||
ui.log(" pisi: yapılandırılacak paket yok, atlanıyor");
|
||||
Ok(())
|
||||
} else {
|
||||
// Post-install betikleri çalışmadı: bu, kurulumun
|
||||
// sessizce bozuk bırakılması anlamına gelir. Kurulumu
|
||||
// durdurup hatayı kullanıcıya göster.
|
||||
Err(format!(
|
||||
"pisi configure-pending başarısız: {}\n\
|
||||
Paketlerin kurulum betikleri çalışmadı; sistem \
|
||||
düzgün başlamayabilir.", e
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,15 +52,19 @@ impl Job for RunLdconfigJob {
|
||||
|
||||
async fn run(&self, ui: &UiSender) -> Result<(), String> {
|
||||
ui.log("ldconfig çalıştırılıyor…");
|
||||
match run_chroot(&self.mount, &["ldconfig"]).await {
|
||||
Ok(_) => ui.log("✓ ldconfig tamamlandı"),
|
||||
Err(e) => ui.log(format!("⚠ ldconfig uyarısı: {}", e)),
|
||||
}
|
||||
run_chroot(&self.mount, &["ldconfig"]).await
|
||||
.map_err(|e| format!("ldconfig başarısız: {}\n\
|
||||
Paylaşımlı kütüphane önbelleği oluşturulamadı.", e))?;
|
||||
ui.log("✓ ldconfig tamamlandı");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// update-environment: PisiLinux init/betiklerini yeniden yapılandırır.
|
||||
///
|
||||
/// Bu adım init sistemi için kritiktir (udev kuralları, servisler,
|
||||
/// /etc/localtime senkronizasyonu). Sessizce yutulursa kurulum "başarılı"
|
||||
/// görünür ama sistem açılmaz — bu yüzden hata fatal.
|
||||
pub struct UpdateEnvironmentJob {
|
||||
pub mount: String,
|
||||
}
|
||||
@@ -65,10 +75,11 @@ impl Job for UpdateEnvironmentJob {
|
||||
|
||||
async fn run(&self, ui: &UiSender) -> Result<(), String> {
|
||||
ui.log("update-environment çalıştırılıyor…");
|
||||
match run_chroot(&self.mount, &["update-environment"]).await {
|
||||
Ok(_) => ui.log("✓ update-environment tamamlandı"),
|
||||
Err(e) => ui.log(format!("⚠ update-environment uyarısı: {}", e)),
|
||||
}
|
||||
run_chroot(&self.mount, &["update-environment"]).await
|
||||
.map_err(|e| format!("update-environment başarısız: {}\n\
|
||||
Init betikleri yapılandırılmadı; kurulu sistem \
|
||||
düzgün başlamayabilir.", e))?;
|
||||
ui.log("✓ update-environment tamamlandı");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -86,29 +97,41 @@ impl Job for CleanupLiveJob {
|
||||
let mt = &self.mount;
|
||||
ui.log("Live ortam izleri temizleniyor…");
|
||||
|
||||
// run/livemedia
|
||||
// Canlı ortamdan kalan dizinler: yoksa hata değil, `let _ =` uygun.
|
||||
let _ = tokio::fs::remove_dir_all(format!("{}/run/livemedia", mt)).await;
|
||||
// var/cache/pisi/packages
|
||||
let _ = tokio::fs::remove_dir_all(format!("{}/var/cache/pisi/packages", mt)).await;
|
||||
let _ = tokio::fs::remove_dir_all(format!("{}/bootmnt", mt)).await;
|
||||
// YALI desktop dosyaları
|
||||
//let _ = tokio::fs::remove_file(format!("{}/usr/share/applications/yali.desktop", mt)).await;
|
||||
//let _ = tokio::fs::remove_file(format!("{}/usr/share/applications/yali-bin.desktop", mt)).await;
|
||||
let _ = tokio::fs::remove_dir_all(format!("{}/bootmnt", mt)).await;
|
||||
//pisi kullanıcısını sil (ev diziniyle birlikte), YALI paketlerini kaldır
|
||||
let _ = run_chroot(&self.mount, &["userdel", "-r", "pisi"]).await;
|
||||
let _ = run_chroot(&self.mount, &["pisi", "rm", "yali-rs"]).await;
|
||||
// live paket deposunu kaldır
|
||||
let _ = run_chroot(&self.mount, &["pisi", "rr", "live"]).await;
|
||||
// depolarları yeniden ekle (stable2 + contrib)
|
||||
ui.log("Depolar Ekleniyor…");
|
||||
let _ = run_chroot(&self.mount, &["pisi", "ar", "stable2", "https://stable2.pisilinux.org/pisi-index.xml.xz"]).await;
|
||||
let _ = run_chroot(&self.mount, &["pisi", "ar", "contrib", "https://contrib.pisilinux.org/pisi-index.xml.xz"]).await;
|
||||
// depoları aktif et
|
||||
ui.log("Depolar aktif ediliyor...");
|
||||
let _ = run_chroot(&self.mount, &["pisi", "er", "contrib", "stable2"]).await;
|
||||
// paket listelerini güncelle
|
||||
ui.log("Depolar Güncelleniyor...");
|
||||
let _ = run_chroot(&self.mount, &["pisi", "ur"]).await;
|
||||
|
||||
// ── Aşağıdakiler FATAL ───────────────────────────────────
|
||||
// Depo tanımları yapılmazsa kurulu sistemde `pisi install`
|
||||
// çalışmaz, yani kullanıcı paket kuramaz. Sessizce yutulursa
|
||||
// kurulum "başarılı" görünür ama sistem kullanılamaz.
|
||||
ui.log("Depolar ekleniyor…");
|
||||
for (name, url) in [
|
||||
("stable2", "https://stable2.pisilinux.org/pisi-index.xml.xz"),
|
||||
("contrib", "https://contrib.pisilinux.org/pisi-index.xml.xz"),
|
||||
] {
|
||||
run_chroot(&self.mount, &["pisi", "ar", name, url]).await
|
||||
.map_err(|e| format!("'{}' deposu eklenemedi: {}\n\
|
||||
Kurulu sistemde paket kurulamaz.", name, e))?;
|
||||
}
|
||||
|
||||
// Depoları etkinleştir
|
||||
ui.log("Depolar etkinleştiriliyor…");
|
||||
run_chroot(&self.mount, &["pisi", "er", "contrib", "stable2"]).await
|
||||
.map_err(|e| format!("Depolar etkinleştirilemedi: {}", e))?;
|
||||
|
||||
// Paket listelerini güncelle (ağ gerektirir, birkaç dakika sürebilir)
|
||||
ui.log("Depolar güncelleniyor (ağ gerekir, biraz sürebilir)…");
|
||||
run_chroot(&self.mount, &["pisi", "ur"]).await
|
||||
.map_err(|e| format!("Paket listeleri güncellenemedi: {}\n\
|
||||
Depolar tanımlı ama indeksleri eksik olabilir.", e))?;
|
||||
|
||||
ui.log("✓ Live temizlik tamamlandı");
|
||||
Ok(())
|
||||
|
||||
+182
-13
@@ -29,6 +29,7 @@
|
||||
|
||||
mod autologin;
|
||||
mod bootloader;
|
||||
mod cleanup;
|
||||
mod execution;
|
||||
mod finish;
|
||||
mod keyboard;
|
||||
@@ -40,6 +41,7 @@ mod users;
|
||||
|
||||
pub use autologin::ConfigureAutologinJob;
|
||||
pub use bootloader::{GenerateInitramfsJob, InstallGrubJob};
|
||||
pub use cleanup::EmergencyCleanupJob;
|
||||
pub use execution::{CopyFilesJob, MountBindJob, UnmountBindJob};
|
||||
pub use finish::{CleanupLiveJob, RunComarJob, RunLdconfigJob, UpdateEnvironmentJob};
|
||||
pub use keyboard::ConfigureKeyboardJob;
|
||||
@@ -165,15 +167,31 @@ pub trait Job: Send + Sync {
|
||||
|
||||
pub struct JobQueue {
|
||||
jobs: Vec<Box<dyn Job>>,
|
||||
/// Başarı da başarısızlık da HER ZAMAN çalıştırılır. Bir iş hata
|
||||
/// verdiğinde bu listeye geçilmeden `return` edilirse hedef bölümler
|
||||
/// bağlı kalır, LUKS mapper'ları açık, swap aktif ve kullanıcı
|
||||
/// kullanılamaz bir canlı sistemde kalır.
|
||||
cleanup: Vec<Box<dyn Job>>,
|
||||
}
|
||||
|
||||
impl JobQueue {
|
||||
pub fn new(jobs: Vec<Box<dyn Job>>) -> Self {
|
||||
Self { jobs }
|
||||
Self { jobs, cleanup: Vec::new() }
|
||||
}
|
||||
|
||||
/// `cleanup` işleri kuyruk BİTSE BİLE hata durumunda da çalıştırılır.
|
||||
pub fn with_cleanup(mut self, cleanup: Vec<Box<dyn Job>>) -> Self {
|
||||
self.cleanup = cleanup;
|
||||
self
|
||||
}
|
||||
|
||||
/// Tüm işleri sırayla çalıştırır.
|
||||
/// İlerleme ve loglar `ui` üzerinden UI'ye iletilir.
|
||||
///
|
||||
/// Hata durumunda: hatayı loglar, `cleanup` işlerini çalıştırır,
|
||||
/// ardından `InstallMessage::Error` gönderir. Temizlik de hata
|
||||
/// verirse bu hatalar orijinal hataya eklenerek raporlanır — böylece
|
||||
/// kullanıcı hem asıl hatayı hem de "ne yapılması gerektiğini" görür.
|
||||
pub async fn run_all(&mut self, ui: UiSender) {
|
||||
let total = self.jobs.len();
|
||||
|
||||
@@ -181,24 +199,52 @@ impl JobQueue {
|
||||
let localized_name = t!(job.name());
|
||||
ui.log(format!("▶ {}", localized_name));
|
||||
|
||||
match job.run(&ui).await {
|
||||
Ok(()) => {
|
||||
ui.log(format!("✓ {}", localized_name));
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = format!("✗ {}: {}", localized_name, e);
|
||||
ui.log(msg.clone());
|
||||
ui.send(InstallMessage::Error(msg));
|
||||
return;
|
||||
}
|
||||
if let Err(e) = job.run(&ui).await {
|
||||
let msg = format!("✗ {}: {}", localized_name, e);
|
||||
ui.log(msg.clone());
|
||||
let report = self.run_cleanup(&ui, &e).await;
|
||||
ui.send(InstallMessage::Error(report));
|
||||
return;
|
||||
}
|
||||
|
||||
ui.log(format!("✓ {}", localized_name));
|
||||
let progress = (i + 1) as f32 / total as f32;
|
||||
ui.send(InstallMessage::Progress(progress));
|
||||
}
|
||||
|
||||
ui.send(InstallMessage::Done);
|
||||
}
|
||||
|
||||
/// Temizlik işlerini çalıştırır; kullanıcıya gösterilecek rapor döndürür.
|
||||
async fn run_cleanup(&self, ui: &UiSender, original_err: &str) -> String {
|
||||
if self.cleanup.is_empty() {
|
||||
return original_err.to_string();
|
||||
}
|
||||
|
||||
ui.log("Kurulum durdu — bağlamalar temizleniyor…");
|
||||
let mut leftovers: Vec<String> = Vec::new();
|
||||
|
||||
for job in &self.cleanup {
|
||||
match job.run(ui).await {
|
||||
Ok(()) => {}
|
||||
Err(e) => leftovers.push(format!("{}: {}", t!(job.name()), e)),
|
||||
}
|
||||
}
|
||||
|
||||
if leftovers.is_empty() {
|
||||
ui.log("✓ Bağlamalar temizlendi");
|
||||
original_err.to_string()
|
||||
} else {
|
||||
ui.log(format!("⚠ {} temizlik sorunu", leftovers.len()));
|
||||
let msg = format!(
|
||||
"{}\n\n--- KURULUM DURDUKTAN SONRA ---\n\
|
||||
Canlı sistemde elle temizlemeniz gerekenler var:\n{}",
|
||||
original_err,
|
||||
leftovers.iter().map(|l| format!(" • {}", l)).collect::<Vec<_>>().join("\n")
|
||||
);
|
||||
msg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
@@ -281,6 +327,90 @@ fn is_mount_point(path: &std::path::Path) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// `/proc/self/mountinfo` alanındaki kaçışlı diziyi çözer.
|
||||
///
|
||||
/// mountinfo, yol içindeki özel karakterleri `\0NNN` **oktal** olarak yazar
|
||||
/// (boşluk `\040`, tab `\011`, satır sonu `\012`, backslash `\134`).
|
||||
/// Çözülmezse "My Disk" gibi bir mountpoint hiçbir koşulla eşleştirilemez.
|
||||
fn unescape_mount_field(s: &str) -> String {
|
||||
let bytes = s.as_bytes();
|
||||
let mut out = Vec::with_capacity(bytes.len());
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
if bytes[i] == b'\\' && i + 3 < bytes.len() + 1 {
|
||||
// `\0NNN` veya `\NNN` biçiminde 3 haneli oktal
|
||||
let (start, width) = if bytes.get(i + 1) == Some(&b'0') { (i + 1, 4) } else { (i + 1, 3) };
|
||||
if start + width <= bytes.len() {
|
||||
let digits = &s[start..start + width];
|
||||
if digits.bytes().all(|b| (b'0'..=b'7').contains(&b)) {
|
||||
if let Ok(v) = u32::from_str_radix(digits, 8) {
|
||||
out.push(v as u8);
|
||||
i = start + width;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out.push(bytes[i]);
|
||||
i += 1;
|
||||
}
|
||||
String::from_utf8_lossy(&out).into_owned()
|
||||
}
|
||||
|
||||
/// Bir bağlama noktasının altındaki TÜM bağlı dosya sistemlerini döndürür,
|
||||
/// en derinden yüzeye doğru sıralı.
|
||||
///
|
||||
/// Neden gerekiyor: `UnmountBindJob` elle yazılmış bir liste
|
||||
/// (`proc`, `sys`, `dev`, `boot/efi`, ...) kullanıyordu. Manuel bölümlemede
|
||||
/// `/home`, `/var`, `/boot` gibi ek bölümler o listede yoktu ve kurulum
|
||||
/// sonrası bağlı kalıyordu. Bu fonksiyon düzeni bilmez — `mount` altındaki
|
||||
/// her şeyi bulur, LUKS/LVM/fuse/loop ne olursa olsun.
|
||||
fn mounted_paths_under(mount: &str) -> Vec<String> {
|
||||
let prefix = format!("{}/", mount.trim_end_matches('/'));
|
||||
let root = mount.trim_end_matches('/').to_string();
|
||||
|
||||
let mut paths: Vec<String> = Vec::new();
|
||||
if let Ok(info) = std::fs::read_to_string("/proc/self/mountinfo") {
|
||||
for line in info.lines() {
|
||||
// mountinfo alanları: id parent major:minor root mountpoint
|
||||
// options [optional...] - fstype source superopts
|
||||
let Some(mp_field) = line.split_whitespace().nth(4) else { continue };
|
||||
let mp = unescape_mount_field(mp_field);
|
||||
if mp == root || mp.starts_with(&prefix) {
|
||||
paths.push(mp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// `/proc/self/mountinfo` kökte de bir satır yoksa kökü de dahil et
|
||||
if !paths.iter().any(|p| p == &root) && is_mount_point(std::path::Path::new(&root)) {
|
||||
paths.push(root.clone());
|
||||
}
|
||||
|
||||
// En derin yol önce: "/mnt/a/b" -> "/mnt/a" -> "/mnt".
|
||||
// Aynı derinlikte ise uzun yol önce.
|
||||
paths.sort_by(|a, b| b.len().cmp(&a.len()));
|
||||
paths.dedup();
|
||||
paths
|
||||
}
|
||||
|
||||
/// LUKS mapper adlarını döndürür (typeseri `crypt` olan aygıtlar).
|
||||
fn open_crypt_mappers() -> Vec<String> {
|
||||
let out = std::process::Command::new("lsblk")
|
||||
.args(["-ndo", "NAME,TYPE"])
|
||||
.output();
|
||||
let Ok(out) = out else { return Vec::new() };
|
||||
String::from_utf8_lossy(&out.stdout)
|
||||
.lines()
|
||||
.filter_map(|l| {
|
||||
let mut it = l.split_whitespace();
|
||||
let name = it.next()?;
|
||||
let kind = it.next()?;
|
||||
(kind == "crypt").then(|| name.to_string())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// `blkid` ile bir bölümün dosya sistemi türünü döndürür.
|
||||
async fn get_fs_type(device: &str) -> Result<String, String> {
|
||||
let output = tokio::process::Command::new("blkid")
|
||||
@@ -511,6 +641,8 @@ pub fn build_full_job_queue(
|
||||
let mt = cfg.mount.clone();
|
||||
let plan2 = plan.clone();
|
||||
let plan3 = plan.clone();
|
||||
// Temizlik için bölüm yolları; `plan` birkaç satır sonra taşınıyor.
|
||||
let cleanup_parts = auto_mode_partitions(&plan);
|
||||
|
||||
// Kaynak dizini: parametre boşsa otomatik tespit et
|
||||
let src = if cfg.source.is_empty() { detect_source_dir() } else { cfg.source };
|
||||
@@ -613,7 +745,30 @@ pub fn build_full_job_queue(
|
||||
// 20. Bind-mount temizle
|
||||
jobs.push(Box::new(UnmountBindJob { mount: mt.clone() }));
|
||||
|
||||
JobQueue::new(jobs)
|
||||
// 21. Son temizlik: LUKS mapper'larını kapat, swap'u indir.
|
||||
// Normal akışta da çalışır — yeniden başlatmadan önce mapper'ın
|
||||
// açık kalması gereksiz ve zararlıdır.
|
||||
jobs.push(Box::new(EmergencyCleanupJob {
|
||||
mount: mt.clone(),
|
||||
partitions: cleanup_parts.clone(),
|
||||
}));
|
||||
|
||||
JobQueue::new(jobs).with_cleanup(vec![Box::new(EmergencyCleanupJob {
|
||||
mount: mt.clone(),
|
||||
partitions: cleanup_parts,
|
||||
})])
|
||||
}
|
||||
|
||||
/// Otomatik modda oluşturulan bölüm yolları (temizlik için).
|
||||
/// Sıra `PartitionJob` ile aynı olmalıdır: EFI → swap → kök.
|
||||
fn auto_mode_partitions(plan: &PartitionPlan) -> Vec<String> {
|
||||
let mut parts = Vec::new();
|
||||
if plan.efi_mb > 0 {
|
||||
parts.push(part_path(&plan.disk, 1));
|
||||
}
|
||||
parts.push(part_path(&plan.disk, if plan.efi_mb > 0 { 2 } else { 1 })); // swap
|
||||
parts.push(part_path(&plan.disk, if plan.efi_mb > 0 { 3 } else { 2 })); // kök
|
||||
parts
|
||||
}
|
||||
|
||||
/// Manuel bölümleme kullanıldığında derlenecek kurulum iş kuyruğu.
|
||||
@@ -686,5 +841,19 @@ pub fn build_custom_job_queue(
|
||||
jobs.push(Box::new(CleanupLiveJob { mount: mt.clone() }));
|
||||
jobs.push(Box::new(UnmountBindJob { mount: mt.clone() }));
|
||||
|
||||
JobQueue::new(jobs)
|
||||
// Son temizlik (bkz. otomatik mod).
|
||||
let cleanup_parts: Vec<String> = custom_partitions.iter()
|
||||
.filter(|p| !p.delete)
|
||||
.map(|p| p.device.clone())
|
||||
.filter(|d| !d.is_empty())
|
||||
.collect();
|
||||
jobs.push(Box::new(EmergencyCleanupJob {
|
||||
mount: mt.clone(),
|
||||
partitions: cleanup_parts.clone(),
|
||||
}));
|
||||
|
||||
JobQueue::new(jobs).with_cleanup(vec![Box::new(EmergencyCleanupJob {
|
||||
mount: mt.clone(),
|
||||
partitions: cleanup_parts,
|
||||
})])
|
||||
}
|
||||
|
||||
+26
-8
@@ -196,6 +196,16 @@ impl Job for PartitionJob {
|
||||
let disk = &self.plan.disk;
|
||||
let is_gpt = self.plan.table_type == PartitionTableType::Gpt;
|
||||
|
||||
// encrypt_root açık ama parola boşsa kök düz ext4 olarak formatlanır,
|
||||
// MountPartitionsJob / GenerateFstabJob ise yalnızca `encrypt_root`
|
||||
// bakıp /dev/mapper/cryptroot'a mount etmeye çalışır → kurulum
|
||||
// yarıda ölür. Bu kombinasyon UI'dan da engellenmeli.
|
||||
if self.plan.encrypt_root && self.plan.luks_password.is_empty() {
|
||||
return Err("Kök şifreleme (LUKS) seçilmiş ancak parola boş. \
|
||||
Lütfen parola girin veya şifrelemeyi kapatın."
|
||||
.to_string());
|
||||
}
|
||||
|
||||
// 0. Diski hazırla: bağlamaları ayır, kernel tablosunu temizle
|
||||
prepare_disk(disk, ui).await;
|
||||
|
||||
@@ -229,11 +239,16 @@ impl Job for PartitionJob {
|
||||
next_mb = swap_end;
|
||||
|
||||
// 4. Kök bölümü (kalan alan)
|
||||
// DİKKAT: parted "MB" sonekini ONDALIK (10^6) okur, "MiB" ikili
|
||||
// (2^20). Hesaplarımız ikili olduğu için daima "MiB" geçilir.
|
||||
// "MB" ile 4609MB = 9001992.1875 sektör → hizasız başlangıç →
|
||||
// "Becerebildiğimiz buna en yakın konum..." hatası. Tamsayı MiB
|
||||
// 2048 sektörün katıdır, hizalama sorunu olmaz.
|
||||
let root_num: u32 = if self.plan.efi_mb > 0 { 3 } else { 2 };
|
||||
ui.log(format!("Kök bölümü oluşturuluyor: {}MiB – disk sonu", next_mb));
|
||||
run_parted(&[
|
||||
"-s", disk, "mkpart", "ext4",
|
||||
&format!("{}MB", next_mb), "100%",
|
||||
&format!("{}MiB", next_mb), "100%",
|
||||
]).await?;
|
||||
|
||||
// 4.5. Tüm mkpart bitti; kernel'e bildir ve aygıt düğümlerini bekle
|
||||
@@ -258,8 +273,9 @@ impl Job for PartitionJob {
|
||||
// LUKS şifreleme (kök bölümü)
|
||||
// Şifre, shell injection'ı önlemek için doğrudan stdin pipe üzerinden
|
||||
// iletilir; `sh -c` + string interpolasyon kullanılmaz.
|
||||
let root_device = if self.plan.encrypt_root && !self.plan.luks_password.is_empty() {
|
||||
let luks_name = "cryptroot";
|
||||
// (Parolanın boş olma durumu yukarıda reddedildi.)
|
||||
let root_device = if self.plan.encrypt_root {
|
||||
let luks_name = super::bootloader::CRYPTROOT_NAME;
|
||||
ui.log(format!("cryptsetup luksFormat {}…", root_part));
|
||||
run_luks_format(&root_part, &self.plan.luks_password).await
|
||||
.map_err(|e| format!("LUKS format hatası: {}", e))?;
|
||||
@@ -297,7 +313,7 @@ impl Job for MountPartitionsJob {
|
||||
let root_part = part_path(disk, root_num);
|
||||
|
||||
let root_device = if self.plan.encrypt_root {
|
||||
"/dev/mapper/cryptroot".to_string()
|
||||
format!("/dev/mapper/{}", super::bootloader::CRYPTROOT_NAME)
|
||||
} else {
|
||||
root_part.clone()
|
||||
};
|
||||
@@ -378,9 +394,10 @@ impl Job for GenerateFstabJob {
|
||||
|
||||
// Kök bölümü
|
||||
if self.encrypt_root {
|
||||
fstab.push_str(
|
||||
"/dev/mapper/cryptroot / ext4 defaults,noatime 0 1\n"
|
||||
);
|
||||
fstab.push_str(&format!(
|
||||
"/dev/mapper/{} / ext4 defaults,noatime 0 1\n",
|
||||
super::bootloader::CRYPTROOT_NAME
|
||||
));
|
||||
} else {
|
||||
fstab.push_str(&format!(
|
||||
"UUID={} / ext4 defaults,noatime 0 1\n",
|
||||
@@ -410,7 +427,8 @@ impl Job for GenerateFstabJob {
|
||||
if self.encrypt_root {
|
||||
let crypttab_path = format!("{}/etc/crypttab", self.mount);
|
||||
let crypttab = format!(
|
||||
"cryptroot UUID={} none luks\n",
|
||||
"{} UUID={} none luks\n",
|
||||
super::bootloader::CRYPTROOT_NAME,
|
||||
self.root_uuid
|
||||
);
|
||||
ui.log(format!("Yazılıyor: {}", crypttab_path));
|
||||
|
||||
+65
-56
@@ -24,7 +24,7 @@ impl Job for CreateUserJob {
|
||||
}
|
||||
|
||||
// useradd: kullanıcıyı temel gruplarla oluştur
|
||||
// PisiLinux'ta "storage" grubu yoksa useradd hata verir; -M flag ile grub yoksa atla
|
||||
// PisiLinux'ta "storage" grubu yoksa useradd hata verir; -M flag ile grup yoksa atla
|
||||
// Güvenli yöntem: önce her grubu tek tek ekle
|
||||
let groups = ["wheel", "users", "pnp", "disk", "audio", "video", "power", "dialout", "lp", "lpadmin", "cdrom", "floppy"];
|
||||
for grp in &groups {
|
||||
@@ -47,37 +47,22 @@ impl Job for CreateUserJob {
|
||||
&self.username,
|
||||
]).await?;
|
||||
|
||||
// // chpasswd ile şifre ayarla (stdin üzerinden güvenli)
|
||||
// ui.log(format!("Şifre ayarlanıyor: {}", self.username));
|
||||
// let chpasswd_input = format!("{}:{}", self.username, self.password);
|
||||
// set_password_via_chpasswd(&self.mount, &chpasswd_input).await?;
|
||||
|
||||
// // Root şifresi: belirlenmişse ayarla, yoksa kilitle
|
||||
// if self.root_password.is_empty() {
|
||||
// ui.log("Root hesabı kilitleniyor…");
|
||||
// run_chroot(&self.mount, &["passwd", "-l", "root"]).await?;
|
||||
// } else {
|
||||
// ui.log("Root şifresi ayarlanıyor…");
|
||||
// let root_input = format!("root:{}", self.root_password);
|
||||
// set_password_via_chpasswd(&self.mount, &root_input).await?;
|
||||
// }
|
||||
|
||||
// 2. chpasswd ile şifre ayarla (YENİ BASİTLEŞTİRİLMİŞ VE KESİN YÖNTEM)
|
||||
// Kullanıcı şifresi: `chpasswd` stdin üzerinden.
|
||||
// `sh -c "echo 'user:pass' | ..."` KULLANILMAZ: parola düz metin
|
||||
// olarak kabuğun argv'sine geçer ve /proc/<pid>/cmdline üzerinden
|
||||
// okunabilir; ayrıca `'` veya `$(...)` içeren parola komutu bozar.
|
||||
// Aynı desen jobs/partition.rs::run_luks_format'ta da kullanılıyor.
|
||||
ui.log(format!("Şifre ayarlanıyor: {}", self.username));
|
||||
// sh -c kullanarak echo ile chpasswd'e şifreyi güvenli bir şekilde üflüyoruz
|
||||
let user_cmd = format!("echo '{}:{}' | chpasswd -c SHA512", self.username, self.password);
|
||||
run_chroot(&self.mount, &["sh", "-c", &user_cmd]).await?;
|
||||
set_password_via_chpasswd(&self.mount, &self.username, &self.password).await?;
|
||||
|
||||
// 3. Root şifresi: belirlenmişse ayarla, yoksa kilitle
|
||||
// Root şifresi: belirlenmişse ayarla, yoksa kilitle
|
||||
if self.root_password.is_empty() {
|
||||
ui.log("Root hesabı kilitleniyor…");
|
||||
run_chroot(&self.mount, &["passwd", "-l", "root"]).await?;
|
||||
} else {
|
||||
ui.log("Root şifresi ayarlanıyor…");
|
||||
let root_cmd = format!("echo 'root:{}' | chpasswd -c SHA512", self.root_password);
|
||||
run_chroot(&self.mount, &["sh", "-c", &root_cmd]).await?;
|
||||
}
|
||||
|
||||
set_password_via_chpasswd(&self.mount, "root", &self.root_password).await?;
|
||||
}
|
||||
|
||||
// sudoers: wheel grubu sudo yetkisi alsın
|
||||
let sudoers_dir = format!("{}/etc/sudoers.d", self.mount);
|
||||
@@ -96,39 +81,63 @@ impl Job for CreateUserJob {
|
||||
}
|
||||
}
|
||||
|
||||
// /// `chpasswd` komutuna stdin üzerinden kullanıcı:şifre gönderir.
|
||||
// async fn set_password_via_chpasswd(mount: &str, input: &str) -> Result<(), String> {
|
||||
// if DEMO_MODE.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
// tokio::time::sleep(std::time::Duration::from_millis(2500)).await;
|
||||
// return Ok(());
|
||||
// }
|
||||
/// `chroot <mount> chpasswd -c SHA512` komutuna `kullanici:sifre` bilgisini
|
||||
/// **stdin pipe'ı** üzerinden iletir.
|
||||
///
|
||||
/// Neden `sh -c` + `format!` değil:
|
||||
/// 1. Parola kabuğun argv'sine yazılırsa `/proc/<pid>/cmdline` üzerinden
|
||||
/// canlı sistemdeki herhangi bir süreç tarafından okunabilir.
|
||||
/// 2. Parola içindeki `'` veya `$(...)` karakterleri komutu bozar ve
|
||||
/// shell injection'a yol açar.
|
||||
/// 3. `format!` ile üretilen komut `user:pass` içinde `:` geçse hatalı
|
||||
/// ayrıştırılır.
|
||||
///
|
||||
/// Bu desen `jobs/partition.rs::run_luks_format` ile birebir aynıdır.
|
||||
async fn set_password_via_chpasswd(
|
||||
mount: &str,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> Result<(), String> {
|
||||
if super::DEMO_MODE.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(2500)).await;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// use tokio::io::AsyncWriteExt;
|
||||
// use tokio::process::Command;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
// let mut child = Command::new("chroot")
|
||||
// .args([mount, "chpasswd"])
|
||||
// .stdin(std::process::Stdio::piped())
|
||||
// .stdout(std::process::Stdio::null())
|
||||
// .stderr(std::process::Stdio::piped())
|
||||
// .spawn()
|
||||
// .map_err(|e| format!("chpasswd başlatılamadı: {}", e))?;
|
||||
let mut child = tokio::process::Command::new("chroot")
|
||||
.args([mount, "chpasswd", "-c", "SHA512"])
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(|e| format!("chpasswd başlatılamadı: {}", e))?;
|
||||
|
||||
// if let Some(mut stdin) = child.stdin.take() {
|
||||
// let mut data = input.as_bytes().to_vec();
|
||||
// if !data.ends_with(b"\n") {
|
||||
// data.push(b'\n');
|
||||
// }
|
||||
// stdin.write_all(&data).await
|
||||
// .map_err(|e| format!("chpasswd stdin yazma hatası: {}", e))?;
|
||||
// }
|
||||
// Parolayı yalnızca çocuk sürecin stdin'ine yaz; argv'de hiç görünmez.
|
||||
if let Some(mut stdin) = child.stdin.take() {
|
||||
// chpasswd "user:pass" satırı bekler ve satır sonu ile ayırır.
|
||||
let mut data = format!("{}:{}\n", username, password).into_bytes();
|
||||
if data.last() != Some(&b'\n') {
|
||||
data.push(b'\n');
|
||||
}
|
||||
let result = stdin.write_all(&data).await;
|
||||
// Yazdıktan sonra parolanın kopyasını bellekte bırakma.
|
||||
data.iter_mut().for_each(|b| *b = 0);
|
||||
drop(stdin);
|
||||
result.map_err(|e| format!("chpasswd stdin yazma hatası: {}", e))?;
|
||||
}
|
||||
|
||||
// let output = child.wait_with_output().await
|
||||
// .map_err(|e| format!("chpasswd bekleme hatası: {}", e))?;
|
||||
let output = child.wait_with_output().await
|
||||
.map_err(|e| format!("chpasswd bekleme hatası: {}", e))?;
|
||||
|
||||
// if output.status.success() {
|
||||
// Ok(())
|
||||
// } else {
|
||||
// Err(String::from_utf8_lossy(&output.stderr).trim().to_string())
|
||||
// }
|
||||
// }
|
||||
if output.status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
let err = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
||||
if err.is_empty() {
|
||||
Err(format!("{} için şifre ayarlanamadı", username))
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+86
-10
@@ -126,11 +126,20 @@ impl RescueStep {
|
||||
let op: u32 = op_str.parse().unwrap_or(0);
|
||||
|
||||
// Tarih: "[2024-03-15 12:00:01]"
|
||||
let date = if let Some(start) = remainder.find('[') {
|
||||
if let Some(end) = remainder.find(']') {
|
||||
remainder[start+1..end].to_string()
|
||||
} else { String::new() }
|
||||
} else { String::new() };
|
||||
// `]` ve `[` bağımsız aranırsa, `]` önce gelen bir girdide
|
||||
// `end < start` olur ve `remainder[start+1..end]` PANİK verir
|
||||
// ("slice index starts at N but ends at M"). Kapanış
|
||||
// parantez, açılıştan SONRA aranmalı.
|
||||
let date = match remainder.find('[') {
|
||||
None => String::new(),
|
||||
Some(open) => {
|
||||
let inner = open + 1;
|
||||
match remainder[inner..].find(']') {
|
||||
Some(rel_close) => remainder[inner..inner + rel_close].to_string(),
|
||||
None => String::new(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Tür + özet: ": install [vim, curl]"
|
||||
let (op_type, summary) = if let Some(colon) = remainder.rfind("]: ") {
|
||||
@@ -372,11 +381,16 @@ impl RescueStep {
|
||||
ui.colored_label(type_color, &entry.op_type);
|
||||
|
||||
// Özet — truncate
|
||||
let summary = if entry.summary.len() > 50 {
|
||||
format!("{}…", &entry.summary[..50])
|
||||
} else {
|
||||
entry.summary.clone()
|
||||
};
|
||||
// `len()` bayt sayar, `&s[..50]` ise bayt
|
||||
// sınırı ister. `pisi history` çıktısı paket
|
||||
// adlarından gelir ve Türkçe karakter içerir
|
||||
// ("İ", "ş", "ğ" 2 bayt): `[..50]` karakter
|
||||
// sınırının ortasına denk gelirse PANİK.
|
||||
// Karakter sayarak kırpmak gerekir.
|
||||
let mut summary: String = entry.summary.chars().take(50).collect();
|
||||
if entry.summary.chars().count() > 50 {
|
||||
summary.push('…');
|
||||
}
|
||||
ui.label(egui::RichText::new(summary).size(11.0).color(theme::c_text_dim()));
|
||||
ui.end_row();
|
||||
}
|
||||
@@ -510,3 +524,65 @@ impl InstallerStep for RescueStep {
|
||||
false // Rescue modu her zaman "tamamlanmamış" — normal kuruluma dönmek için rescue_mode=false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Özet kırpma: Türkçe karakterler 2 bayt olduğu için bayt tabanlı
|
||||
/// dilimleme karakter sınırının ortasına denk gelip panik verirdi.
|
||||
fn truncate(s: &str, max: usize) -> String {
|
||||
let mut out: String = s.chars().take(max).collect();
|
||||
if s.chars().count() > max {
|
||||
out.push('…');
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncate_handles_multibyte_without_panicking() {
|
||||
// "şğüöç" 5 karakter × 20 = 100 karakter, 200 bayt. 50 karakterlik
|
||||
// kesme bayt sınırının tam ortasına denk gelirdi ve `&s[..50]`
|
||||
// PANİK verirdi.
|
||||
let s = "şğüöç".repeat(20);
|
||||
assert_eq!(s.chars().count(), 100);
|
||||
let t = truncate(&s, 50);
|
||||
assert_eq!(t.chars().count(), 51, "50 karakter + '…'");
|
||||
assert!(t.ends_with('…'));
|
||||
assert!(t.chars().take(50).all(|c| "şğüöç".contains(c)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncate_leaves_short_strings_untouched() {
|
||||
assert_eq!(truncate("kısa", 50), "kısa");
|
||||
assert_eq!(truncate("", 50), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_history_handles_stray_close_bracket() {
|
||||
// "] Operation ..." — ']' '[' tan önce geliyor. Eski kod
|
||||
// remainder[start+1..end] ile PANİK verirdi.
|
||||
let out = RescueStep::parse_pisi_history("Operation 5 ] bogus: install [vim]");
|
||||
assert_eq!(out.len(), 1, "girdi hâlâ işlenmeli");
|
||||
assert_eq!(out[0].op, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_history_reads_date_and_summary() {
|
||||
let raw = "Operation 42 [2024-03-15 12:00:01]: install [vim, curl]";
|
||||
let out = RescueStep::parse_pisi_history(raw);
|
||||
assert_eq!(out.len(), 1);
|
||||
assert_eq!(out[0].op, 42);
|
||||
assert_eq!(out[0].date, "2024-03-15 12:00:01");
|
||||
assert_eq!(out[0].op_type, "install");
|
||||
// Özet, "]: " sonrasındaki ilk boşluktan itibaren alınır; paket
|
||||
// listesi köşeli parantezleriyle birlikte kalır (mevcut davranış).
|
||||
assert_eq!(out[0].summary, "[vim, curl]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_history_tolerates_unterminated_bracket() {
|
||||
let out = RescueStep::parse_pisi_history("Operation 7 [2024-03-15 12:00:01: install [x]");
|
||||
assert_eq!(out.len(), 1, "panik vermemeli, kayıt üretmeli");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user