From 8d652c3a99ec6b10d01f0efa9fd9c50755d77e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erkan=20I=C5=9EIK?= Date: Fri, 25 Sep 2026 23:15:57 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20squashfs=20ak=C4=B1=C5=9F=C4=B1na?= =?UTF-8?q?=20uygun=20sihirbaz=C4=B1=20daralt,=20DM=20tespitini=20=C3=A7al?= =?UTF-8?q?=C4=B1=C5=9Fma=20zaman=C4=B1na=20al?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit YALI kurulu sistemi ISO'nun squashfs'inden kopyalar (CopyFilesJob rsync). Bu yüzden ISO'da hangi paketler, ekran yöneticisi ve masaüstü ortamı varsa kurulum onları ZATEN içerir. Sihirbazın bunları seçmesi anlamsızdı: kullanıcının seçimi kopyalama sırasında ezilecekti. ## 1. Sihirbaz 13 → 10 adım LicenseStep, DisplayManagerStep ve NetinstallStep listeden çıkarıldı. Silinmedi: steps/mod.rs'te tanımlı, derleniyor, hazır. Her biri için etkinleştirme koşulu mod.rs'te yazılı (netinstall için ayrıca selected_package_groups doldurulmalı ve InstallExtraPackagesJob kuyruğa alınmalı). İzin, 22de7d5'in yaptığının tersine SADECE bu üç modüle ve SADECE belgelenmiş kasıtlı uyku için kondu — modül dışındaki taze ölü kod yine uyarı üretir. ## 2. ConfigureAutologinJob ISO'nun DM'ini eziyordu Alanın doc yorumu "yapılandırma ISO'da hazır olduğu için burada değiştirilmez" diyordu; kod tam tersini yapıyordu. state.display_manager GlobalState'in SABİT "sddm" varsayılanı (kullanıcı seçemiyor), ona göre dallanılıyordu. ISO lightdm kullanıyorsa lightdm.conf'a hiç dokunulmaz, sddm.conf yazılır ve otomatik giriş sessizce çalışmazdı. - detect_display_manager(): hedefte hangi DM'in kurulu olduğunu çalışma zamanında arar (/usr/bin/, yoksa yapılandırma dizini). Bu iş CopyFilesJob'dan sonra çalıştığı için mount doludur. - sddm config dizini: /usr/lib/sddm/sddm.conf.d yoksa /etc/sddm.conf.d deniyor; ikisi de yoksa hata (sessizce yanlış yere yazmak yerine). - lightdm'de autologin-session ham DE adıyla ("plasma") yazılıyordu; sddm/gdm'nin kullandığı .desktop eşlemesi (session_desktop_file) kullanılıyor. lightdm oturumu dosya adıyla arar, "plasma" ile oturum açılmazdı. - DM bulunamazsa hata veriyor (kullanıcıya "elle yapılandırın" diyebilmeli), eskiden sadece uyarıp geçiyordu. ## 3. Özet ekranı doğru bilgiyi gösteriyor state.display_manager sabitini olduğu gibi gösteriyordu → ISO lightdm iken "sddm" yazıyordu. Artık yalnızca kullanıcının gerçekten belirlediği şey (otomatik giriş açık/kapalı) gösteriliyor ve DM'in görüntüden geldiği belirtiliyor. Yanındaki hardcoded İngilizce parçalar ("password protected", "manual login") de t!() altına alındı. ## 4. Cevap dosyasında (auto-install) sabit değerler JobQueueConfig alanları answer file'dan hiç okunmuyordu; hepsi main.rs'te yazılmıştı: display_manager "sddm", desktop_environment "plasma", autologin TRUE, root_password boş, bootloader_timeout 5. - İsteğe bağlı [system] bölümü eklendi: autologin, bootloader_* ve kernel_options. [user]'a root_password. - autologin varsayılanı true → false. Gözetimsiz kurulumda açık olması, kurulan sistemde konsola erişebilen herkesin kullanıcı adını görmesini ve oturumu açmasını kolaylaştırıyordu. - desktop_environment artık SABİT "plasma" değil, GlobalState'in canlı ortam tespiti. XFCE ISO'sunda Plasma oturumu açılmaya çalışılıyordu. - answer-example.toml güncellendi, iki dilde açıklama eklendi. ### Bulunan ve düzeltilen tuzak SystemConfig #[derive(Default)] ile 0 döndürüyordu. [system] bloğu dosyada HİÇ YOKSA serde alan bazlı `#[serde(default = "...")]` anotasyonlarına uğramaz, doğrudan Default::default() çağırır — yani GRUB bekleme süresi 0 saniye olurdu ve kurulumda bootloader menüsü hiç görünmezdi. Default elle uygulandı; regresyon testi eklendi. ## 5. Testler +7: autologin (2), answer file ayrıştırma (4, biri depodaki gerçek dosyayı ayrıştırıyor, biri varsayılan tuzağını kilitliyor), wizard (zaten 4'tü). cargo check --all-targets : 0 hata, 0 uyarı cargo test : 55/55 geçti (47 → 55) Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 24 +++--- README_en.md | 24 +++--- answer-example.toml | 33 ++++++++ locales/en.toml | 4 + locales/tr.toml | 4 + src/autoinstall/mod.rs | 164 ++++++++++++++++++++++++++++++++++++++++ src/jobs/autologin.rs | 167 +++++++++++++++++++++++++++++++++++------ src/main.rs | 57 +++++++++----- src/steps/mod.rs | 60 +++++++++++++-- src/steps/summary.rs | 30 +++++--- 10 files changed, 485 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index e751ec2..e3ec026 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ Rust ve egui ile yeniden yazılmış **Pisi GNU/Linux** kurulum aracı. YALI, ca ## Özellikler -- **13 adımlı kurulum sihirbazı**: Hoş Geldiniz → Lisans → Konum → Klavye → Ağ → Disk Bölümleme → Kullanıcılar → Ekran Yöneticisi → Bootloader → Ek Paketler → Özet → Kurulum → Bitir +- **10 adımlı kurulum sihirbazı**: Hoş Geldiniz → Konum → Klavye → Ağ → Disk Bölümleme → Kullanıcılar → Bootloader → Özet → Kurulum → Bitir +- **Squashfs tabanlı**: kurulu sistem ISO'nun squashfs'inden kopyanır, paketler ağdan kurulmaz. Ekran yöneticisi, masaüstü ortamı ve paketler ISO'da geldiği için sihirbaz bunları **seçmez** (seçseydi kopyalama zaten ezdiği için yanıltıcı olurdu) - **İki mod**: Grafik arayüz (varsayılan) ve cevap dosyasıyla otomatik kurulum (`--auto-install answer.toml`) - **Demo modu**: Sistem kontrollerini atlar, kurulumu simüle eder (`--demo`) - **Disk bölümleme**: Otomatik (GPT/MBR) veya manuel, LVM ve LUKS şifreleme desteği @@ -94,18 +95,15 @@ cargo run --bin slides_test [desktop] [lang] ## Kurulum İş Akışı 1. **Hoş Geldiniz** — Dil seçimi, sistem kontrolleri, kurtarma modu -2. **Lisans** — GPL metni ve kabul onayı -3. **Konum / Zaman** — Saat dilimi seçimi (bölge/alan çift ComboBox), GeoIP, NTP veya manuel tarih/saat -4. **Klavye** — Düzen/varyant seçimi, arama, canlı önizleme (setxkbmap) -5. **Ağ** — WiFi tarama, bağlantı, proxy -6. **Disk Bölümleme** — Otomatik (rehberli) veya manuel, LVM, LUKS -7. **Kullanıcılar** — Hesap oluşturma, parola gücü göstergesi, root parolası, otomatik oturum açma -8. **Ekran Yöneticisi** — DM ve masaüstü ortamı seçimi -9. **Bootloader** — GRUB aygıtı, zaman aşımı, parola, çekirdek parametreleri -10. **Ek Paketler** — Paket grubu seçimi (ofis, multimedya, geliştirme, …) -11. **Özet** — Tüm seçimlerin gözden geçirilmesi -12. **Kurulum** — Slayt + ilerleme çubuğu + canlı log -13. **Bitir** — Başarılı kurulum, yeniden başlat veya canlı masaüstüne dön +2. **Konum / Zaman** — Saat dilimi seçimi (bölge/alan çift ComboBox), GeoIP, NTP veya manuel tarih/saat +3. **Klavye** — Düzen/varyant seçimi, arama, canlı önizleme (setxkbmap) +4. **Ağ** — WiFi tarama, bağlantı, proxy +5. **Disk Bölümleme** — Otomatik (rehberli) veya manuel, LVM, LUKS +6. **Kullanıcılar** — Hesap oluşturma, parola gücü göstergesi, root parolası, otomatik oturum açma +7. **Bootloader** — GRUB aygıtı, zaman aşımı, parola, çekirdek parametreleri +8. **Özet** — Tüm seçimlerin gözden geçirilmesi +9. **Kurulum** — Slayt + ilerleme çubuğu + canlı log +10. **Bitir** — Başarılı kurulum, yeniden başlat veya canlı masaüstüne dön ## Bağımlılıklar diff --git a/README_en.md b/README_en.md index 18c5d38..c5f7d37 100644 --- a/README_en.md +++ b/README_en.md @@ -4,7 +4,8 @@ A rewritten **Pisi GNU/Linux** installation tool in Rust and egui. YALI installs ## Features -- **13-step installation wizard**: Welcome → License → Location → Keyboard → Network → Disk Partitioning → Users → Display Manager → Bootloader → Extra Packages → Summary → Installation → Finish +- **10-step installation wizard**: Welcome → Location → Keyboard → Network → Disk Partitioning → Users → Bootloader → Summary → Installation → Finish +- **Squashfs-based**: the installed system is copied from the ISO's squashfs, packages are not fetched over the network. The display manager, desktop environment and packages come from the ISO, so the wizard does **not** offer them (offering them would be misleading, since the copy would overwrite the choice) - **Two modes**: Graphical interface (default) and automatic installation with answer file (`--auto-install answer.toml`) - **Demo mode**: Skips system checks, simulates installation (`--demo`) - **Disk partitioning**: Automatic (GPT/MBR) or manual, LVM and LUKS encryption support @@ -92,18 +93,15 @@ cargo run --bin slides_test [desktop] [lang] ## Installation Workflow 1. **Welcome** — Language selection, system checks, rescue mode -2. **License** — GPL text and acceptance -3. **Location / Time** — Timezone selection (region/area dual ComboBox), GeoIP, NTP or manual date/time -4. **Keyboard** — Layout/variant selection, search, live preview (setxkbmap) -5. **Network** — WiFi scanning, connection, proxy -6. **Disk Partitioning** — Automatic (guided) or manual, LVM, LUKS -7. **Users** — Account creation, password strength indicator, root password, autologin -8. **Display Manager** — DM and desktop environment selection -9. **Bootloader** — GRUB device, timeout, password, kernel parameters -10. **Extra Packages** — Package group selection (office, multimedia, development, …) -11. **Summary** — Review all selections -12. **Installation** — Slideshow + progress bar + live log -13. **Finish** — Successful installation, reboot or return to live desktop +2. **Location / Time** — Timezone selection (region/area dual ComboBox), GeoIP, NTP or manual date/time +3. **Keyboard** — Layout/variant selection, search, live preview (setxkbmap) +4. **Network** — WiFi scanning, connection, proxy +5. **Disk Partitioning** — Automatic (guided) or manual, LVM, LUKS +6. **Users** — Account creation, password strength indicator, root password, autologin +7. **Bootloader** — GRUB device, timeout, password, kernel parameters +8. **Summary** — Review all selections +9. **Installation** — Slideshow + progress bar + live log +10. **Finish** — Successful installation, reboot or return to live desktop ## Dependencies diff --git a/answer-example.toml b/answer-example.toml index c63364d..e61ce7f 100644 --- a/answer-example.toml +++ b/answer-example.toml @@ -36,3 +36,36 @@ password = "changeme123" # Bilgisayar adı: harf, rakam ve - ; tire ile başlayamaz/bitemez hostname = "pisilinux" + +# Kök parolası (isteğe bağlı). +# Boş bırakılırsa kök hesap KİLİTLENİR (passwd -l root). +root_password = "" + + +# ───────────────────────────────────────────── +# [system] — tamamen isteğe bağlı. +# Bu bölümü hiç yazmazsanız tüm ayarlar güvenli varsayılanlarda kalır. +# ───────────────────────────────────────────── +[system] +# Otomatik oturum açma (varsayılan: false). +# +# Gözetimsiz kurulumda açık bırakmayın: kurulan sistemde konsola +# erişebilen herkes giriş ekranında kullanıcı adını görür ve +# oturumu açabilir. Ekran yöneticisi ISO'dan gelir; bu ayar yalnızca +# mevcut oturum yöneticisine "bu kullanıcıyı otomatik aç" yazar. +autologin = false + +# GRUB menü bekleme süresi, saniye (varsayılan: 5) +bootloader_timeout = 5 + +# GRUB parolası (isteğe bağlı). Hedefte PBKDF2 ile hashlenir. +# UYARI: Bu dosyayı güvenli tutun; düz metin olarak saklanır. +bootloader_password = "" + +# Ek çekirdek parametreleri (ör. "quiet splash nomodeset") +kernel_options = "" + +# NOT: Ekran yöneticisi ve masaüstü ortamı BU DOSYADA AYARLANMAZ. +# YALI kurulu sistemi ISO'nun squashfs'inden kopyar, dolayısıyla +# bunlar zaten görüntüde bulunurlar; kurulum anında otomatik tespit +# edilirler. Ek paket kurma (netinstall) da bu akışın parçası değildir. diff --git a/locales/en.toml b/locales/en.toml index 85a9284..5a8b6eb 100644 --- a/locales/en.toml +++ b/locales/en.toml @@ -128,6 +128,10 @@ summary_software = "Software" summary_display_manager = "Display Manager" summary_autologin = "Autologin" summary_autologin_user = "Autologin User" +summary_autologin_on = "On" +summary_autologin_off = "Off (password login)" +summary_bootloader_password = "password protected" +summary_desktop_from_image = "Comes from the ISO image; detected automatically during install" summary_desktop = "Desktop" # ── Display Manager ─────────────────────────────── diff --git a/locales/tr.toml b/locales/tr.toml index c5037f9..785695d 100644 --- a/locales/tr.toml +++ b/locales/tr.toml @@ -285,6 +285,10 @@ summary_software = "Yazılım" summary_display_manager = "Görüntü Yöneticisi" summary_autologin = "Otomatik oturum açma" summary_autologin_user = "Otomatik oturum açılacak kullanıcı" +summary_autologin_on = "Açık" +summary_autologin_off = "Kapalı (parola ile giriş)" +summary_bootloader_password = "parola korumalı" +summary_desktop_from_image = "ISO görüntüsünden gelir; kurulumda otomatik tespit edilir" summary_desktop = "Masaüstü" # ── Görüntü Yöneticisi ─────────────────────────────────────── diff --git a/src/autoinstall/mod.rs b/src/autoinstall/mod.rs index 86c1b5d..ff13c5d 100644 --- a/src/autoinstall/mod.rs +++ b/src/autoinstall/mod.rs @@ -33,6 +33,53 @@ pub struct UserConfig { pub password: String, #[serde(default = "default_hostname")] pub hostname: String, + /// Kök hesabın parolası. Boş bırakılırsa root kilitlenir + /// (`CreateUserJob` → `passwd -l root`). + #[serde(default)] + pub root_password: String, +} + +/// `[system]` bölümü — isteğe bağlı davranış ayarları. +/// +/// Tümü `#[serde(default)]`: answer dosyası yalnızca zorunlu alanları +/// içerebilir. Bu bölümü tanımlamak zorunlu değil. +#[derive(Debug, serde::Deserialize)] +pub struct SystemConfig { + /// Otomatik oturum açma. + /// + /// Varsayılan KAPALI. Gözetimsiz kurulumda açık olması, kurulmuş + /// sistemde herhangi birinin fiziksel olarak konsola oturup + /// kullanıcı adını görmesini kolaylaştırır. + #[serde(default)] + pub autologin: bool, + /// GRUB bekleme süresi (saniye). + #[serde(default = "default_grub_timeout")] + pub bootloader_timeout: u32, + /// GRUB parolası (düz metin; hedefte PBKDF2 ile hashlenir). + #[serde(default)] + pub bootloader_password: String, + /// Ek çekirdek parametreleri. + #[serde(default)] + pub kernel_options: String, +} + +// DİKKAT: burada `#[derive(Default)]` KULLANILMAZ. +// +// `[system]` bloğu answer dosyasında HİÇ YOKSA serde, AnswerFile'daki +// `#[serde(default)]` sayesinde bu tipin Default'ını çağırır. Alan +// bazlı `#[serde(default = "...")]` anotasyonları yalnızca blok +// KATILDIĞINDA, ilgili alan eksik olduğunda devreye girer. Yani +// türetilmiş Default, bootloader_timeout'ı 0 yapardı → kurulumda GRUB +// menüsü hiç görünmez. Elle uyguluyoruz. +impl Default for SystemConfig { + fn default() -> Self { + Self { + autologin: false, + bootloader_timeout: default_grub_timeout(), + bootloader_password: String::new(), + kernel_options: String::new(), + } + } } /// Answer file'ın tamamı (`--auto-install `). @@ -40,6 +87,9 @@ pub struct UserConfig { pub struct AnswerFile { pub install: InstallConfig, pub user: UserConfig, + /// İsteğe bağlı. Verilmezse tüm ayarlar güvenli varsayılanlarda kalır. + #[serde(default)] + pub system: SystemConfig, } fn default_timezone() -> String { "Europe/Istanbul".to_string() } @@ -48,6 +98,7 @@ fn default_keyboard() -> String { "tr".to_string() } fn default_source() -> String { "/run/livecd/squashfs-root/".to_string() } fn default_mount() -> String { "/mnt".to_string() } fn default_hostname() -> String { "pisilinux".to_string() } +fn default_grub_timeout() -> u32 { 5 } // ───────────────────────────────────────────── // ANSWER FILE: YÜKLE ve UYGULA @@ -105,11 +156,28 @@ pub fn apply_to_state(af: &AnswerFile, state: &mut crate::installer::GlobalState state.username = af.user.username.clone(); state.password = af.user.password.clone(); state.password_confirm = af.user.password.clone(); + state.root_password = af.user.root_password.clone(); + state.root_password_confirm = af.user.root_password.clone(); state.hostname = af.user.hostname.clone(); state.selected_disk = Some(af.install.disk.clone()); state.erase_disk = true; state.language = if af.install.locale.starts_with("tr") { "tr" } else { "en" }.to_string(); + // [system] — isteğe bağlı davranış ayarları + state.autologin = af.system.autologin; + state.bootloader_timeout = af.system.bootloader_timeout; + state.bootloader_password = af.system.bootloader_password.clone(); + state.kernel_options = af.system.kernel_options.clone(); + // GRUB aygıtı: bölümleme yapılmadan önce bilinmez, kuruluma + // InstallGrubJob içinde atanır. + state.bootloader_device = String::new(); + + // Masaüstü ortamı ve ekran yöneticisi answer file'dan GELMEZ. + // Sistem squashfs'ten kopyalandığı için bunlar ISO'nun kendisidir; + // GlobalState::default() zaten canlı ortamı tespit ediyor + // (detect_desktop_environment). Burada sabit bir değer yazmak, + // XFCE ISO'sunda Plasma oturumu açmaya çalışırdı. + // Disk boyutunu lsblk ile sorgula let disk_info: Option = (|| { let out = std::process::Command::new("lsblk") @@ -165,3 +233,99 @@ pub fn apply_to_state(af: &AnswerFile, state: &mut crate::installer::GlobalState } } + +#[cfg(test)] +mod tests { + use super::*; + + const MINIMAL: &str = r#" +[install] +disk = "/dev/sda" + +[user] +username = "pisi" +password = "gizli123" +hostname = "pisi-pc" +"#; + + const WITH_SYSTEM: &str = r#" +[install] +disk = "/dev/nvme0n1" + +[user] +username = "pisi" +password = "gizli123" +root_password = "rootgizli" + +[system] +autologin = true +bootloader_timeout = 10 +bootloader_password = "grubparola" +kernel_options = "quiet splash" +"#; + + #[test] + fn minimal_answer_file_parses() { + // [system] tamamen isteğe bağlı — yoksa ayrıştırma patlamamalı. + let af = toml::from_str::(MINIMAL).expect("ayrıştırılmalı"); + assert_eq!(af.install.disk, "/dev/sda"); + assert_eq!(af.user.username, "pisi"); + assert_eq!(af.user.root_password, "", "varsayılan boş olmalı"); + assert!(!af.system.autologin, "autologin güvenli varsayılanı kapalı"); + assert_eq!(af.system.bootloader_timeout, 5); + assert!(af.system.bootloader_password.is_empty()); + assert!(af.system.kernel_options.is_empty()); + } + + #[test] + fn system_section_is_honoured() { + let af = toml::from_str::(WITH_SYSTEM).expect("ayrıştırılmalı"); + assert_eq!(af.install.disk, "/dev/nvme0n1"); + assert_eq!(af.user.root_password, "rootgizli"); + assert!(af.system.autologin, "autologin açık olmalı"); + assert_eq!(af.system.bootloader_timeout, 10); + assert_eq!(af.system.bootloader_password, "grubparola"); + assert_eq!(af.system.kernel_options, "quiet splash"); + } + + #[test] + fn example_file_on_disk_is_valid() { + // Depodaki örnek dosya gerçekten ayrıştırılabiliyor mu? + let p = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("answer-example.toml"); + let content = std::fs::read_to_string(&p) + .expect("answer-example.toml okunmalı"); + toml::from_str::(&content) + .expect("answer-example.toml ayrıştırılabilmeli"); + } + + #[test] + fn bootloader_timeout_default_survives_missing_system_block() { + // Regresyon: SystemConfig türetilmiş Default'a sahipti ve 0 + // veriyordu. `[system]` bloğu dosyada HİÇ YOKSA serde alan + // bazlı `#[serde(default = "...")]` anotasyonlarına UĞRAMAZ; + // doğrudan Default::default() çağırır. Sonuç: GRUB bekleme + // süresi 0 saniye → kurulumda bootloader menüsü hiç görünmez. + let af = toml::from_str::(MINIMAL).unwrap(); + assert_eq!( + af.system.bootloader_timeout, 5, + "[system] yokken de varsayılan geçerli olmalı" + ); + assert_eq!(SystemConfig::default().bootloader_timeout, 5); + } + + #[test] + fn default_impl_matches_serde_field_defaults() { + // İki yol da aynı sonucu vermeli. + let d = SystemConfig::default(); + assert!(!d.autologin); + assert!(d.bootloader_password.is_empty()); + assert!(d.kernel_options.is_empty()); + } + + #[test] + fn missing_required_section_is_rejected() { + // [user] olmadan kabul edilmemeli. + let bad = "[install]\ndisk = \"/dev/sda\"\n"; + assert!(toml::from_str::(bad).is_err()); + } +} diff --git a/src/jobs/autologin.rs b/src/jobs/autologin.rs index 45465fe..079910c 100644 --- a/src/jobs/autologin.rs +++ b/src/jobs/autologin.rs @@ -24,31 +24,64 @@ impl Job for ConfigureAutologinJob { return Ok(()); } + // Ekran yöneticisini SEÇMEK yerine TESPİT ediyoruz. + // + // YALI kurulu sistemi squashfs'ten kopyalar; ekran yöneticisi ISO'da + // hazırdır. `state.display_manager` ise GlobalState'in "sddm" + // varsayılanıdır ve kullanıcı tarafından değiştirilemez. Önceden + // bu sabite göre dallanılıyordu: ISO lightdm kullanıyorsa + // lightdm.conf'a hiç dokunulmaz, sddm.conf yazılır ve otomatik + // giriş sessizce çalışmazdı. + let dm = detect_display_manager(&self.mount).await; + match &dm { + Some(found) if found != &self.display_manager => { + ui.log(format!( + "Ekran yöneticisi tespit edildi: {} (varsayılan: {} kullanılmayacak)", + found, self.display_manager + )); + } + Some(found) => { + ui.log(format!("Ekran yöneticisi: {}", found)); + } + None => { + return Err( + "Hedef sistemde sddm/lightdm/gdm bulunamadı. \ + Otomatik oturum açma uygulanamadı; kurulumda seçilen \ + oturum yöneticisini elle yapılandırın." + .to_string(), + ) + } + } + ui.log(format!( "Otomatik oturum açma uygulanıyor: {} ({})", - self.username, self.display_manager + self.username, self.username )); - let session = match self.desktop_environment.as_str() { - "plasma" => "plasmax11.desktop", - "xfce" => "xfce.desktop", - "gnome" => "gnome.desktop", - "cinnamon" => "cinnamon.desktop", - "budgie" => "budgie-desktop.desktop", - "lxde" => "LXDE.desktop", - "lxqt" => "lxqt.desktop", - "mate" => "mate.desktop", - "lumina" => "lumina-desktop.desktop", - _ => "plasmax11.desktop", - }; + let session = session_desktop_file(&self.desktop_environment); - match self.display_manager.as_str() { - "sddm" => { + match dm.as_deref() { + Some("sddm") => { // ISO'nun /usr/lib/sddm/sddm.conf.d içeriğine dokunmadan // ayrı bir drop-in dosyası ile autologin eklenir. let dir = format!("{}/usr/lib/sddm/sddm.conf.d", self.mount); + // Bu dizin yoksa ISO sddm kullanmıyordur demektir; sddm + // genelde /etc/sddm.conf.d kullanır. İkisini de dene. + let dir = if std::path::Path::new(&dir).is_dir() { + dir + } else { + let alt = format!("{}/etc/sddm.conf.d", self.mount); + if std::path::Path::new(&alt).is_dir() { + alt + } else { + return Err(format!( + "sddm yapılandırma dizini bulunamadı ({}/usr/lib/sddm/sddm.conf.d, {}/etc/sddm.conf.d)", + self.mount, self.mount + )); + } + }; tokio::fs::create_dir_all(&dir).await - .map_err(|e| format!("sddm.conf.d oluşturulamadı: {}", e))?; + .map_err(|e| format!("{} oluşturulamadı: {}", dir, e))?; let content = format!( "[Autologin]\nUser={}\nSession={}\n", self.username, session @@ -57,7 +90,7 @@ impl Job for ConfigureAutologinJob { .await .map_err(|e| format!("autologin.conf yazılamadı: {}", e))?; } - "lightdm" => { + Some("lightdm") => { let dir = format!("{}/etc/lightdm", self.mount); let _ = tokio::fs::create_dir_all(&dir).await; let conf = format!("{}/lightdm.conf", dir); @@ -68,14 +101,17 @@ impl Job for ConfigureAutologinJob { content.push_str("[Seat:*]\n"); } if !content.contains("autologin-user=") { + // lightdm oturumu .desktop dosyası adı olarak bekler. + // Ham DE adını ("plasma") yazmak, sddm/gdm'nin kullandığı + // eşlemenin aynısı değildi ve oturumu bulamazdı. content.push_str(&format!("autologin-user={}\n", self.username)); - content.push_str(&format!("autologin-session={}\n", self.desktop_environment)); + content.push_str(&format!("autologin-session={}\n", session)); } tokio::fs::write(&conf, content) .await .map_err(|e| format!("lightdm.conf yazılamadı: {}", e))?; } - "gdm" => { + Some("gdm") => { let dir = format!("{}/etc/gdm", self.mount); let _ = tokio::fs::create_dir_all(&dir).await; let conf = format!("{}/custom.conf", dir); @@ -87,10 +123,11 @@ impl Job for ConfigureAutologinJob { .await .map_err(|e| format!("gdm/custom.conf yazılamadı: {}", e))?; } - _ => { - ui.log(format!( - "⚠ Bilinmeyen ekran yöneticisi: {} — autologin uygulanmadı", - self.display_manager + other => { + return Err(format!( + "Desteklenmeyen ekran yöneticisi tespit edildi: {} — \ + otomatik oturum açma uygulanmadı", + other.unwrap_or("bilinmiyor") )); } } @@ -99,3 +136,87 @@ impl Job for ConfigureAutologinJob { Ok(()) } } + +/// Masaüstü ortamı adını, oturum yöneticilerinin beklediği `.desktop` +/// dosyası adına eşler. +/// +/// lightdm, sddm ve gdm oturumu `plasmax11.desktop` gibi bir dosya adıyla +/// arar; ham DE adıyla ("plasma") yazmak oturumu bulamaz. +fn session_desktop_file(de: &str) -> &'static str { + match de { + "plasma" => "plasmax11.desktop", + "xfce" => "xfce.desktop", + "gnome" => "gnome.desktop", + "cinnamon" => "cinnamon.desktop", + "budgie" => "budgie-desktop.desktop", + "lxde" => "LXDE.desktop", + "lxqt" => "lxqt.desktop", + "mate" => "mate.desktop", + "lumina" => "lumina-desktop.desktop", + // Bilinmeyen DE'de plasma varsayılır. Önemli olan, ham adı + // yazıp oturumu bulamamaktır. + _ => "plasmax11.desktop", + } +} + +/// Hedef sistemde hangi ekran yöneticisinin KURULU olduğunu tespit eder. +/// +/// YALI squashfs'ten kopyaladığı için ekran yöneticisi ISO'nun kendisidir; +/// kullanıcı seçmez. Tespit çalışma zamanında yapılır çünkü `mount` +/// dizini rsync'ten sonra tamdır (bu iş `CopyFilesJob`'dan sonra çalışır). +/// +/// İki kaynak kontrol edilir: çalıştırılabilir (`/usr/bin/sddm`) ve +/// yapılandırma dosyası. Çoğu dağıtımda ikisi de vardır ama paket +/// yöneticisine göre değişebilir. +async fn detect_display_manager(mount: &str) -> Option { + for (bin, name) in [ + ("sddm", "sddm"), + ("lightdm", "lightdm"), + ("gdm", "gdm"), + ] { + let exe = format!("{}/usr/bin/{}", mount, bin); + if std::path::Path::new(&exe).exists() { + return Some(name.to_string()); + } + } + // Yedek: yapılandırma dizini + for (dir, name) in [ + ("etc/sddm.conf.d", "sddm"), + ("usr/lib/sddm", "sddm"), + ("etc/lightdm", "lightdm"), + ("etc/gdm", "gdm"), + ] { + if std::path::Path::new(&format!("{}/{}", mount, dir)).is_dir() { + return Some(name.to_string()); + } + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn every_de_maps_to_a_desktop_file() { + // lightdm/sddm/gdm oturumu .desktop dosyası arar; ham ad + // yazılırsa oturum açılmaz. + for de in [ + "plasma", "xfce", "gnome", "cinnamon", "budgie", + "lxde", "lxqt", "mate", "lumina", + ] { + let f = session_desktop_file(de); + assert!(f.ends_with(".desktop"), "{} → {} uzantısız", de, f); + } + } + + #[test] + fn unknown_de_falls_back_to_a_valid_desktop_file() { + for de in ["", "kde5", "deepin", "TOTALLY-UNKNOWN"] { + assert!( + session_desktop_file(de).ends_with(".desktop"), + "{} geçersiz dosya adı veriyor", de + ); + } + } +} diff --git a/src/main.rs b/src/main.rs index d8b7f5e..2e17884 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ mod branding; mod funct; use installer::{GlobalState, InstallerStep, WelcomeStep}; -use steps::{LocationStep, KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, NetworkStep, DisplayManagerStep, NetinstallStep, LicenseStep}; +use steps::{LocationStep, KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, NetworkStep}; use eframe::egui; use rust_i18n::t; @@ -128,6 +128,7 @@ fn run_auto_install(path: &str, demo_mode: bool) -> eframe::Result<()> { let timezone = af.install.timezone.clone(); let username = af.user.username.clone(); let password = af.user.password.clone(); + let root_password = af.user.root_password.clone(); let hostname = af.user.hostname.clone(); let kb_layout = af.install.keyboard.clone(); let kb_variant = af.install.keyboard_variant.clone(); @@ -135,6 +136,16 @@ fn run_auto_install(path: &str, demo_mode: bool) -> eframe::Result<()> { let mount = af.install.mount.clone(); let plan = state.partition_plan.unwrap(); + // Squashfs kurulumu: oturum yöneticisi ve masaüstü ortamı ISO'dan + // gelir. `apply_to_state` bunları GlobalState'in canlı ortam + // tespitine bırakıyor; burada sabit yazmıyoruz. + let display_manager = state.display_manager.clone(); + let desktop_environment = state.desktop_environment.clone(); + let autologin = af.system.autologin; + let boot_timeout = af.system.bootloader_timeout; + let boot_password = af.system.bootloader_password.clone(); + let kernel_options = af.system.kernel_options.clone(); + let rt = tokio::runtime::Runtime::new().expect("tokio runtime"); rt.block_on(async { use std::sync::mpsc; @@ -176,14 +187,16 @@ fn run_auto_install(path: &str, demo_mode: bool) -> eframe::Result<()> { kb_variant: kb_variant_c, demo_mode, boot_device: String::new(), - bootloader_timeout: 5, - bootloader_password: String::new(), - kernel_options: String::new(), + bootloader_timeout: boot_timeout, + bootloader_password: boot_password, + kernel_options: kernel_options, use_ntp: true, - root_password: String::new(), - display_manager: "sddm".to_string(), - autologin: true, - desktop_environment: "plasma".to_string(), + root_password: root_password, + // Tespit edilmiş değerler (ISO'nun kendisi) — sabit + // yazılmıyor. + display_manager, + autologin, + desktop_environment, selected_package_groups: Vec::new(), manual_year: 2026, manual_month: 5, @@ -245,20 +258,27 @@ impl Default for YaliApp { let branding = branding::BrandingConfig::load_for_lang(&default_lang); let mut app = Self { state: GlobalState::default(), + // Sihirbaz akışı: yalı KURULU SİSTEMİ squashfs'ten diske + // kopyalar (CopyFilesJob rsync). Bu yüzden ISO'da hangi + // paketler, ekran yöneticisi ve masaüstü ortamı varsa + // kurulum BUNLARI ZATEN İÇERİR. + // + // Bu yüzden LicenseStep, DisplayManagerStep ve NetinstallStep + // bilinçli olarak listede DEĞİL: + // - lisans kabulü bu akışta anlam taşımıyor + // - ekran yöneticisi ISO'da pişmiş geliyor + // - ağdan ek paket kurmak, diski squashfs'ten kopyalamayla + // çelişir; netinstall ayrı bir proje olarak ileride + // devreye alınacak + // Adımlar silinmedi — mod.rs'te tanımlı ve hazır duruyor. steps: vec![ Box::new(WelcomeStep::default()), - // Orijinal Python Yali'de sıra: info → license → mediaCheck - // → keyboardSetup. Rust yeniden yazımında LicenseStep yazıldı - // ama hiç bağlanmadı (mod.rs'te tanımsızdı). - Box::new(LicenseStep), Box::new(LocationStep::default()), Box::new(KeyboardStep::default()), Box::new(NetworkStep::default()), Box::new(PartitionStep::default()), Box::new(UsersStep::default()), - Box::new(DisplayManagerStep), Box::new(BootloaderStep::default()), - Box::new(NetinstallStep), Box::new(SummaryStep), Box::new(ExecutionStep::default()), Box::new(FinishStep), @@ -540,15 +560,12 @@ mod tests { fn wizard() -> Vec> { vec![ Box::new(WelcomeStep::default()), - Box::new(LicenseStep), Box::new(LocationStep::default()), Box::new(KeyboardStep::default()), Box::new(NetworkStep::default()), Box::new(PartitionStep::default()), Box::new(UsersStep::default()), - Box::new(DisplayManagerStep), Box::new(BootloaderStep::default()), - Box::new(NetinstallStep), Box::new(SummaryStep), Box::new(ExecutionStep::default()), Box::new(FinishStep), @@ -597,13 +614,17 @@ mod tests { /// sonrası yaşanan regresyon sınıfının tersidir: orada liste /// değişti ve konumsal `cur == n_steps - 2` tespiti sessizce /// kaydı. + /// + /// Örnek olarak `NetinstallStep` kullanıldı: bu adım squashfs + /// kurulumunda bilinçli olarak listede değil, ama netinstall + /// projesi başladığında aynen buraya eklenebilmeli. #[test] fn roles_survive_inserting_a_step_before_execution() { let mut steps = wizard(); let exec_at = steps.iter().position(|s| s.role() == StepRole::Execution).unwrap(); // Yeni bir Normal adım ekle - steps.insert(exec_at, Box::new(NetinstallStep)); + steps.insert(exec_at, Box::new(steps::netinstall::NetinstallStep)); let n = steps.len(); // Hâlâ doğru adımlar son ikide mi? diff --git a/src/steps/mod.rs b/src/steps/mod.rs index d86f98f..99b3d74 100644 --- a/src/steps/mod.rs +++ b/src/steps/mod.rs @@ -1,3 +1,17 @@ +//! Sihirbaz adımları. +//! +//! ## Aktif akış +//! +//! YALI, **KURULU SİSTEMİ squashfs'ten diske kopyalar** (`jobs::CopyFilesJob` +//! rsync). Bunun temel sonucu: ISO'da hangi paketler, ekran yöneticisi ve +//! masaüstü ortamı varsa, kurulum onları ZATEN içerir. Bu yüzden sihirbaz +//! paket seçimi, ekran yöneticisi seçimi veya lisans kabulü sunmaz — +//! sunduğu ayarları kopyalama zaten ezdiği için yanıltıcı olurdu. +//! +//! Aktif sıra (`main.rs::YaliApp::default`): +//! Welcome → Location → Keyboard → Network → Partition → Users → +//! Bootloader → Summary → Execution → Finish + pub mod keyboard; pub mod users; pub mod summary; @@ -7,9 +21,6 @@ pub mod partition; pub mod bootloader; pub mod network; pub mod rescue; -pub mod display_manager; -pub mod netinstall; -pub mod license; pub mod location; pub use keyboard::KeyboardStep; @@ -21,7 +32,44 @@ pub use partition::PartitionStep; pub use bootloader::BootloaderStep; pub use network::NetworkStep; pub use rescue::RescueStep; -pub use display_manager::DisplayManagerStep; -pub use netinstall::NetinstallStep; -pub use license::LicenseStep; pub use location::LocationStep; + +// ───────────────────────────────────────────── +// DORMANT ADIMLAR — squashfs akışının parçası DEĞİL +// +// Derleniyorlar ve hazırlar, ama sihirbaza BAĞLI DEĞİLLER. main.rs'deki +// `steps` vektörüne eklenmedikleri için kullanıcıya hiç gösterilmezler. +// Eklenmeleri gerektiğinde tek satır yeterlidir: `InstallerStep::role()` +// varsayılan `Normal` döndüğü için konumsal bir güncelleme gerekmez. +// +// NOT — `#[allow(dead_code)]` yalnızca bu üç modüle, yalnızca aşağıdaki +// kasıtlı uyku için konmuştur. 22de7d5 bu gerekçeyi vermeden genel +// `#[allow(dead_code)]` serpiştirmiş ve 1804 satırlık bir silmeyi +// "dead code temizliği" diye gizlemişti. Buradaki izin yalnızca bu +// modüllere ve yalnızca aşağıdaki nedenle sınırlıdır; modül dışında +// taze bir ölü kod yine uyarı üretir. +// ───────────────────────────────────────────── + +/// Ekran yöneticisi / masaüstü ortamı seçimi. +/// ISO'nun ekran yöneticisi squashfs'ten geldiği için seçim anlamsız — +/// seçilen değer `ConfigureAutologinJob` tarafından ISO'dakini ezerdi. +/// Etkinleştirmek için: main.rs'de Users ile Bootloader arasına +/// `steps::display_manager::DisplayManagerStep` ekle. +#[allow(dead_code)] +pub mod display_manager; + +/// Ağdan ek paket kurma (netinstall). +/// Squashfs kurulumuyla çelişir: paketler zaten görüntüde. Netinstall +/// ayrı bir proje olarak başlatıldığında bu adım `main.rs`'de +/// Bootloader ile Summary arasına +/// `steps::netinstall::NetinstallStep` olarak eklenmeli, +/// `JobQueueConfig::selected_package_groups` doldurulmalı ve +/// `InstallExtraPackagesJob` kuyruğa alınmalı. `src/jobs/netinstall.rs` +/// bu iş için hazır. +#[allow(dead_code)] +pub mod netinstall; + +/// GPL lisans metni ve kabul onayı. +/// Bu kurulum akışında lisans kabulü bir koşul değil. +#[allow(dead_code)] +pub mod license; diff --git a/src/steps/summary.rs b/src/steps/summary.rs index f763f04..bab55db 100644 --- a/src/steps/summary.rs +++ b/src/steps/summary.rs @@ -137,18 +137,30 @@ impl InstallerStep for SummaryStep { summary_row(ui, t!("summary_datetime"), &dt_str); // Bootloader - let bl_str = format!("{} ({}s timeout{})", - state.bootloader_device, - state.bootloader_timeout, - if state.bootloader_password.is_empty() { "" } else { ", password protected" } + let pw_suffix = if state.bootloader_password.is_empty() { + String::new() + } else { + format!(", {}", t!("summary_bootloader_password")) + }; + let bl_str = format!( + "{} ({}s timeout{})", + state.bootloader_device, state.bootloader_timeout, pw_suffix ); summary_row(ui, t!("summary_bootloader"), &bl_str); - // Display Manager - let dm_str = format!("{} / {} ({})", - state.display_manager, - state.desktop_environment, - if state.autologin { t!("summary_autologin") } else { std::borrow::Cow::Borrowed("manual login") } + // Oturum yöneticisi ve masaüstü ortamı + // + // Kullanıcı bunları SEÇEMEZ: sistem squashfs'ten + // kopyalandığı için ISO'nun kendi DM'i gelir ve + // ConfigureAutologinJob kurulum anında tespit eder. + // `state.display_manager` GlobalState'in sabit "sddm" + // varsayılanıdır; burada göstermek yanlış bilgi olurdu. + // Gösterilebilen tek şey, kullanıcının belirlediği + // otomatik giriş tercihi. + let dm_str = format!( + "{} — {}", + if state.autologin { t!("summary_autologin_on") } else { t!("summary_autologin_off") }, + t!("summary_desktop_from_image") ); summary_row(ui, t!("summary_display_manager"), &dm_str);