diff --git a/locales/en.toml b/locales/en.toml index c30c123..cc25c68 100644 --- a/locales/en.toml +++ b/locales/en.toml @@ -90,6 +90,7 @@ finish_title = "Installation Complete" # ── Execution (extra) ───────────────────────────────────── install_failed = "Installation failed" install_done = "Installation complete! Click Next to continue." +install_current_file = "Current file" retry_install = "Retry" finish_next = "Finish →" diff --git a/locales/tr.toml b/locales/tr.toml index e51e863..d685ea7 100644 --- a/locales/tr.toml +++ b/locales/tr.toml @@ -89,6 +89,7 @@ finish_title = "Kurulum Tamamlandı" # ── Execution (ek) ──────────────────────────────────────── install_failed = "Kurulum başarısız" install_done = "Kurulum tamamlandı! Devam etmek için İleri'ye basın." +install_current_file = "İşlemdeki dosya" retry_install = "Yeniden Dene" finish_next = "Tamamlandı →" diff --git a/src/autoinstall/mod.rs b/src/autoinstall/mod.rs index f4d67de..36183c2 100644 --- a/src/autoinstall/mod.rs +++ b/src/autoinstall/mod.rs @@ -137,11 +137,12 @@ pub fn apply_to_state(af: &AnswerFile, state: &mut crate::installer::GlobalState let efi_mb = if is_uefi { 512u64 } else { 0 }; let total_mb = info.size_bytes / 1024 / 1024; - // Tavsiye edilen swap: RAM * 1.5, min 512 MB, max 8192 MB + // Swap: RAM * 1.0, min 512 MB, max 2048 MB, ayrıca diskin %10'unu geçmesin let mut sys = sysinfo::System::new(); sys.refresh_memory(); - let ram_mb = sys.total_memory() / 1024 / 1024; - let swap_mb = (ram_mb * 3 / 2).clamp(512, 8192); + let ram_mb = sys.total_memory() / 1024 / 1024; + let disk_10pct = total_mb / 10; + let swap_mb = ram_mb.clamp(512, 2048).min(disk_10pct); let overhead = efi_mb + swap_mb + 512; let root_mb = total_mb.saturating_sub(overhead); diff --git a/src/installer.rs b/src/installer.rs index 300f575..96dad24 100644 --- a/src/installer.rs +++ b/src/installer.rs @@ -205,6 +205,7 @@ pub struct GlobalState { pub available_disks: Vec, pub install_progress: f32, pub install_log: Vec, + pub install_current_file: Option, pub demo_mode: bool, pub is_dark: bool, pub rescue_mode: bool, @@ -271,6 +272,7 @@ impl Default for GlobalState { available_disks: Vec::new(), install_progress: 0.0, install_log: Vec::new(), + install_current_file: None, demo_mode: false, is_dark: true, rescue_mode: false, diff --git a/src/jobs/mod.rs b/src/jobs/mod.rs index e22f510..c3d83ea 100644 --- a/src/jobs/mod.rs +++ b/src/jobs/mod.rs @@ -129,17 +129,42 @@ impl Job for CopyFilesJob { async fn run(&self, ui: &UiSender) -> Result<(), String> { ui.log(format!("rsync {} → {}", self.source, self.target)); - // --info=progress2 ham çıktı verir; gerçek uygulamada - // satır satır parse edilerek ilerleme güncellenebilir. + let is_root_source = self.source.trim_end_matches('/') == ""; + let mut args: Vec = vec![ "-aAXH".to_string(), "--info=progress2".to_string(), - "--exclude=/proc".to_string(), - "--exclude=/sys".to_string(), - "--exclude=/dev".to_string(), - "--exclude=/run".to_string(), + "--exclude=/proc/**".to_string(), + "--exclude=/sys/**".to_string(), + "--exclude=/dev/**".to_string(), + "--exclude=/run/**".to_string(), + "--exclude=/tmp/**".to_string(), + "--exclude=/var/tmp/**".to_string(), + "--exclude=/var/cache/**".to_string(), + "--exclude=/home/**".to_string(), + "--exclude=/root/**".to_string(), + "--exclude=/lost+found".to_string(), + "--exclude=*.sqfs".to_string(), ]; + // Kaynak canlı sistem köküyse ekstra exclude'ler + if is_root_source { + args.extend_from_slice(&[ + "--exclude=/bootmnt/**".to_string(), + "--exclude=/ro_branch/**".to_string(), + "--exclude=/mnt/**".to_string(), + "--exclude=/media/**".to_string(), + "--exclude=/var/log/journal/**".to_string(), + "--exclude=/var/lib/pisi/cache/**".to_string(), + "--exclude=/var/lib/pisi/archives/**".to_string(), + "--exclude=/etc/adjtime".to_string(), + "--exclude=/etc/mtab".to_string(), + "--exclude=/etc/resolv.conf".to_string(), + "--exclude=/etc/hostname".to_string(), + "--exclude=/etc/machine-id".to_string(), + ]); + } + if let (Ok(source_path), Ok(target_path)) = ( std::path::Path::new(&self.source).canonicalize(), std::path::Path::new(&self.target).canonicalize(), @@ -743,6 +768,26 @@ async fn sync_partition_table(disk: &str) { /// 2. Sabit aday listesini dene (her adayda bin/ + etc/ varlığı aranır) /// 3. Hiçbiri bulunamazsa `/` (canlı sistem kökü) kullan — rsync /// zaten /proc /sys /dev /run'u exclude eder. +fn has_root_fs(path: &std::path::Path) -> bool { + (path.join("bin").exists() || path.join("usr/bin").exists()) && path.join("etc").exists() +} + +fn is_iso_mount(path: &str) -> bool { + let sqfs_check = [ + "pisi/pisi.sqfs", + "live/pisi.sqfs", + "live/filesystem.squashfs", + "casper/filesystem.squashfs", + "live squashfs", + ]; + for pattern in &sqfs_check { + if std::path::Path::new(path).join(pattern).exists() { + return true; + } + } + false +} + pub fn detect_source_dir() -> String { // 1. /proc/mounts içinde squashfs veya overlay mount noktalarını tara if let Ok(mounts) = std::fs::read_to_string("/proc/mounts") { @@ -756,7 +801,7 @@ pub fn detect_source_dir() -> String { "squashfs" => { if mount_pt != "/" { let p = std::path::Path::new(mount_pt); - if p.join("bin").exists() || p.join("usr/bin").exists() { + if has_root_fs(p) { return format!("{}/", mount_pt.trim_end_matches('/')); } } @@ -768,7 +813,7 @@ pub fn detect_source_dir() -> String { if let Some(lower) = opt.strip_prefix("lowerdir=") { for dir in lower.split(':') { let p = std::path::Path::new(dir); - if p.exists() && (p.join("bin").exists() || p.join("usr/bin").exists()) { + if p.exists() && has_root_fs(p) { return format!("{}/", dir.trim_end_matches('/')); } } @@ -781,7 +826,8 @@ pub fn detect_source_dir() -> String { } // 2. Sabit aday listesi (her adayda bin/ + etc/ kontrolü) - let candidates = [ + // NOT: /bootmnt ISO kökü olabilir; altında pisi.sqfs varsa atlanır. + for path in &[ "/run/livecd/squashfs-root", "/run/live/rootfs", "/run/live/medium", @@ -791,19 +837,19 @@ pub fn detect_source_dir() -> String { "/mnt/cdrom", "/bootmnt/live", "/bootmnt/livecd", - "/bootmnt", - ]; - for path in &candidates { + ] { let p = std::path::Path::new(path); - if p.exists() { - let has_bin = p.join("bin").exists() || p.join("usr/bin").exists(); - let has_etc = p.join("etc").exists(); - if has_bin && has_etc { - return format!("{}/", path); - } + if p.exists() && has_root_fs(p) { + return format!("{}/", path); } } + // 2b. /bootmnt son çare; ama ISO köküyse (pisi.sqfs varsa) atla + let bootmnt = std::path::Path::new("/bootmnt"); + if bootmnt.exists() && !is_iso_mount("/bootmnt") && has_root_fs(bootmnt) { + return "/bootmnt/".to_string(); + } + // 3. Hiçbiri bulunamazsa canlı sistem kökü "/".to_string() } @@ -1324,6 +1370,8 @@ impl Job for CleanupLiveJob { // 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; + ui.log("✓ Live temizlik tamamlandı"); Ok(()) @@ -1661,20 +1709,37 @@ 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?; + // // 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 + // // 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) + 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?; + + // 3. 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?; - } + let root_cmd = format!("echo 'root:{}' | chpasswd -c SHA512", self.root_password); + run_chroot(&self.mount, &["sh", "-c", &root_cmd]).await?; +} + // sudoers: wheel grubu sudo yetkisi alsın let sudoers_dir = format!("{}/etc/sudoers.d", self.mount); @@ -1693,6 +1758,43 @@ 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(()); +// } + +// use tokio::io::AsyncWriteExt; +// use tokio::process::Command; + +// 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))?; + +// 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))?; +// } + +// 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()) +// } +// } + /// `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) { @@ -1705,6 +1807,7 @@ async fn set_password_via_chpasswd(mount: &str, input: &str) -> Result<(), Strin let mut child = Command::new("chroot") .args([mount, "chpasswd"]) + // Gerekirse varsayılan algoritmayı garantiye almak için araya "-c", "SHA512" argümanları eklenebilir. .stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::piped()) @@ -1712,10 +1815,24 @@ async fn set_password_via_chpasswd(mount: &str, input: &str) -> Result<(), Strin .map_err(|e| format!("chpasswd başlatılamadı: {}", e))?; if let Some(mut stdin) = child.stdin.take() { - stdin.write_all(input.as_bytes()).await + let mut data = input.as_bytes().to_vec(); + if !data.ends_with(b"\n") { + data.push(b'\n'); + } + + // 1. Veriyi tampon belleğe yaz + stdin.write_all(&data).await .map_err(|e| format!("chpasswd stdin yazma hatası: {}", e))?; + + // 2. KRİTİK EKSİK: Tampondaki veriyi chpasswd sürecine zorla gönder (Sıvazla) + stdin.flush().await + .map_err(|e| format!("chpasswd stdin flush hatası: {}", e))?; + + // 3. Kanalı elinle kapatarak chpasswd'e "bitti" (EOF) sinyali yolla + drop(stdin); } + // wait_with_output zaten arkada kalan süreçleri temizler ve çıktıları toplar let output = child.wait_with_output().await .map_err(|e| format!("chpasswd bekleme hatası: {}", e))?; @@ -1724,7 +1841,7 @@ async fn set_password_via_chpasswd(mount: &str, input: &str) -> Result<(), Strin } else { Err(String::from_utf8_lossy(&output.stderr).trim().to_string()) } -} +} /// fstab'ı doğrudan dosyaya yazar (genfstab yerine; ISO ortamında daha güvenilir). pub struct GenerateFstabJob { diff --git a/src/steps/execution.rs b/src/steps/execution.rs index 4375033..b328f68 100644 --- a/src/steps/execution.rs +++ b/src/steps/execution.rs @@ -41,6 +41,7 @@ impl InstallerStep for ExecutionStep { state.install_progress = 0.0; state.install_log.clear(); + state.install_current_file = None; state.install_log.push(t!("install_starting").to_string()); } @@ -289,6 +290,15 @@ impl InstallerStep for ExecutionStep { ); }); + if let Some(ref current_file) = state.install_current_file { + ui.add_space(4.0); + ui.label( + egui::RichText::new(format!("{}: {}", t!("install_current_file"), current_file)) + .size(12.0) + .color(theme::c_text_dim()), + ); + } + ui.add_space(4.0); ui.add( egui::ProgressBar::new(state.install_progress) diff --git a/src/steps/finish.rs b/src/steps/finish.rs index 2004d89..4ca82b4 100644 --- a/src/steps/finish.rs +++ b/src/steps/finish.rs @@ -39,9 +39,13 @@ impl InstallerStep for FinishStep { theme::primary_button(&t!("finish_restart")) .min_size(egui::vec2(200.0, 44.0)), ).clicked() { - let _ = std::process::Command::new("systemctl") - .arg("reboot") - .spawn(); + ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close); + std::thread::spawn(|| { + std::thread::sleep(std::time::Duration::from_millis(500)); + let _ = std::process::Command::new("reboot") + .arg("-f") + .spawn(); + }); } ui.add_space(12.0);