mirror of
https://gitlab.com/erkanisik/yali-rs.git
synced 2026-07-31 03:09:00 +00:00
feat: implement real-name to username auto-derivation, add root privilege check, and improve manual partitioning UI with smart defaults.
This commit is contained in:
+1
-1
@@ -15,7 +15,7 @@ pisi_logo_dark = "assets/pisi-logo-dark.png"
|
||||
pisi_logo_light = "assets/pisi-logo-light.png"
|
||||
|
||||
# Title of the installation window
|
||||
title = "PiSi Linux Installation"
|
||||
title = "YALI Installation Wizard"
|
||||
|
||||
# Subtitle displayed in the left sidebar
|
||||
subtitle = "YALI"
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ pisi_logo_dark = "assets/pisi-logo-dark.png"
|
||||
pisi_logo_light = "assets/pisi-logo-light.png"
|
||||
|
||||
# Kurulum penceresinin başlığı (pencere çubuğu)
|
||||
title = "PiSi Linux Kurulumu"
|
||||
title = "YALI Kurulum Aracı"
|
||||
|
||||
# Sol kenar çubuğunda görüntülenen dağıtım ismi
|
||||
subtitle = "YALI"
|
||||
|
||||
@@ -45,6 +45,7 @@ erase_warning = "WARNING: All data on the selected disk will be erased"
|
||||
users = "Users"
|
||||
users_title = "User Account"
|
||||
users_description = "Create a user account for your system."
|
||||
real_name_label = "Full Name"
|
||||
username_label = "Username"
|
||||
password_label = "Password"
|
||||
password_confirm_label = "Confirm password"
|
||||
@@ -154,6 +155,10 @@ pwd_very_strong = "Very strong"
|
||||
use_same_for_admin = "Use the same password for the administrator (root) account"
|
||||
|
||||
# ── Checker ───────────────────────────────────────────────
|
||||
check_root = "Permissions"
|
||||
check_root_pass = "Running with root privileges."
|
||||
check_root_fail = "Not running as root! The installer requires root privileges. Please run with 'sudo yali-rs' or 'pkexec yali-rs'."
|
||||
|
||||
check_disk_space = "Disk Space"
|
||||
check_disk_fail = "Maximum disk size is %{size} GB. Minimum 10 GB is required for installation."
|
||||
check_disk_warn = "%{size} GB disk available. 20 GB or more recommended."
|
||||
@@ -248,6 +253,7 @@ mp_error_empty_mountpoint = "Mount point cannot be empty."
|
||||
mp_plan_valid = "✓ Partition plan is valid. You can proceed."
|
||||
mp_select_disk_first = "← Select a disk first."
|
||||
mp_add_dialog_hint = "/ or /home or swap"
|
||||
mp_select_hint = "Select…"
|
||||
mp_add_btn = "✓ Add"
|
||||
mp_size_hint = "Size (MB, 0=remaining):"
|
||||
mp_delete_confirm_yes = "✓ Yes, Delete"
|
||||
|
||||
+7
-1
@@ -45,6 +45,7 @@ erase_warning = "UYARI: Seçilen diskteki tüm veriler silinecektir"
|
||||
users = "Kullanıcı"
|
||||
users_title = "Kullanıcı Bilgileri"
|
||||
users_description = "Sisteminiz için bir kullanıcı hesabı oluşturun."
|
||||
real_name_label = "Ad Soyad"
|
||||
username_label = "Kullanıcı adı"
|
||||
password_label = "Şifre"
|
||||
password_confirm_label = "Şifre (tekrar)"
|
||||
@@ -86,7 +87,7 @@ retry_install = "Yeniden Dene"
|
||||
finish_next = "Tamamlandı →"
|
||||
|
||||
# ── Finish ────────────────────────────────────────────────
|
||||
finish_description = "PisiLinux başarıyla kuruldu. Şimdi sisteminizi yeniden başlatabilirsiniz."
|
||||
finish_description = "Pisi GNU/Linux başarıyla kuruldu. Şimdi sisteminizi yeniden başlatabilirsiniz."
|
||||
finish_restart = "🔄 Yeniden Başlat"
|
||||
finish_live = "Canlı Masaüstüne Dön"
|
||||
install_in_progress = "Kurulum devam ediyor, lütfen bekleyin…"
|
||||
@@ -131,6 +132,10 @@ pwd_strong = "Güçlü"
|
||||
pwd_very_strong = "Çok güçlü"
|
||||
|
||||
# ── Checker ───────────────────────────────────────────────
|
||||
check_root = "Yetki"
|
||||
check_root_pass = "Root yetkisi mevcut."
|
||||
check_root_fail = "Root yetkisi yok! Kurulum için yönetici (root) olarak çalıştırılmalıdır. Lütfen 'sudo yali-rs' veya 'pkexec yali-rs' ile tekrar deneyin."
|
||||
|
||||
check_disk_space = "Disk Alanı"
|
||||
check_disk_fail = "Maksimum disk boyutu %{size} GB. Kurulum için minimum 10 GB gerekli."
|
||||
check_disk_warn = "%{size} GB disk mevcut. 20 GB veya üzeri önerilir."
|
||||
@@ -220,6 +225,7 @@ mp_error_empty_mountpoint = "Bağlama noktası boş olamaz."
|
||||
mp_plan_valid = "✓ Bölüm planı geçerli. İleri tuşuna basabilirsiniz."
|
||||
mp_select_disk_first = "← Önce bir disk seçin."
|
||||
mp_add_dialog_hint = "/ veya /home veya swap"
|
||||
mp_select_hint = "Seç…"
|
||||
mp_add_btn = "✓ Ekle"
|
||||
mp_size_hint = "Boyut (MB, 0=kalan):"
|
||||
mp_delete_confirm_yes = "✓ Evet, Sil"
|
||||
|
||||
@@ -39,6 +39,7 @@ impl CheckResult {
|
||||
|
||||
pub fn run_all() -> Vec<CheckResult> {
|
||||
vec![
|
||||
check_root(),
|
||||
check_ram(),
|
||||
check_disk_space(),
|
||||
check_internet(),
|
||||
@@ -234,6 +235,29 @@ fn check_live_env() -> CheckResult {
|
||||
}
|
||||
}
|
||||
|
||||
/// Kök kullanıcı (root) yetkisi kontrolü.
|
||||
/// mount, swapon, parted gibi komutlar root gerektirdiğinden
|
||||
/// installer'ın root olarak çalıştırılması zorunludur.
|
||||
fn check_root() -> CheckResult {
|
||||
let is_root = std::process::Command::new("id")
|
||||
.arg("-u")
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|o| {
|
||||
if o.status.success() {
|
||||
Some(String::from_utf8_lossy(&o.stdout).trim() == "0")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if is_root {
|
||||
CheckResult::pass(t!("check_root"), t!("check_root_pass"))
|
||||
} else {
|
||||
CheckResult::fail(t!("check_root"), t!("check_root_fail"))
|
||||
}
|
||||
}
|
||||
|
||||
/// NVMe ve diğer modern disk arayüzlerinin çekirdek desteğini kontrol eder.
|
||||
fn check_nvme_support() -> CheckResult {
|
||||
// /dev/nvme* cihazları var mı?
|
||||
|
||||
+45
-13
@@ -2,6 +2,48 @@ use eframe::egui;
|
||||
use sysinfo::System;
|
||||
use rust_i18n::t;
|
||||
|
||||
/// Aktif masaüstü ortamını algılar.
|
||||
/// Önce `XDG_CURRENT_DESKTOP` ortam değişkenine bakar (normal kullanıcı).
|
||||
/// sudo ile çalışıldığında env var kaybolur, bu durumda `pgrep` ile
|
||||
/// çalışan desktop session process'lerini kontrol eder.
|
||||
pub fn detect_desktop_environment() -> String {
|
||||
// 1. XDG_CURRENT_DESKTOP ortam değişkeni
|
||||
if let Ok(de) = std::env::var("XDG_CURRENT_DESKTOP") {
|
||||
let de = de.trim().to_lowercase();
|
||||
if !de.is_empty() {
|
||||
return de;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. sudo ile çalışıldıysa env kaybolur — aktif session process'lerini kontrol et
|
||||
let de_map: &[(&str, &[&str])] = &[
|
||||
("plasma", &["plasmashell", "startplasma"]),
|
||||
("xfce", &["xfce4-session", "xfdesktop"]),
|
||||
("gnome", &["gnome-session"]),
|
||||
("cinnamon", &["cinnamon-session"]),
|
||||
("budgie", &["budgie-wm"]),
|
||||
("lxqt", &["lxqt-session"]),
|
||||
("mate", &["mate-session"]),
|
||||
("lumina", &["lumina-desktop"]),
|
||||
("lxde", &["lxsession"]),
|
||||
];
|
||||
for (de_name, procs) in de_map {
|
||||
for proc in *procs {
|
||||
if let Ok(out) = std::process::Command::new("pgrep")
|
||||
.args(["-x", proc])
|
||||
.output()
|
||||
{
|
||||
if out.status.success() {
|
||||
return de_name.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Hiçbiri bulunamazsa varsayılan
|
||||
"plasma".to_string()
|
||||
}
|
||||
|
||||
/// Disk adı ve bölüm numarasından tam aygıt yolunu oluşturur.
|
||||
///
|
||||
/// - SATA/SCSI: `/dev/sda` + 1 → `/dev/sda1`
|
||||
@@ -154,6 +196,7 @@ pub struct GlobalState {
|
||||
pub logical_volumes: Vec<LogicalVolume>,
|
||||
pub keyboard_layout: String,
|
||||
pub keyboard_variant: String,
|
||||
pub real_name: String,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub password_confirm: String,
|
||||
@@ -169,7 +212,6 @@ pub struct GlobalState {
|
||||
pub bootloader_timeout: u32,
|
||||
pub bootloader_password: String,
|
||||
pub kernel_options: String,
|
||||
pub license_accepted: bool,
|
||||
pub root_password: String,
|
||||
pub root_password_confirm: String,
|
||||
pub display_manager: String,
|
||||
@@ -217,6 +259,7 @@ impl Default for GlobalState {
|
||||
logical_volumes: Vec::new(),
|
||||
keyboard_layout: "tr".to_string(),
|
||||
keyboard_variant: String::new(),
|
||||
real_name: String::new(),
|
||||
username: String::new(),
|
||||
password: String::new(),
|
||||
password_confirm: String::new(),
|
||||
@@ -232,7 +275,6 @@ impl Default for GlobalState {
|
||||
bootloader_timeout: 5,
|
||||
bootloader_password: String::new(),
|
||||
kernel_options: String::new(),
|
||||
license_accepted: false,
|
||||
root_password: String::new(),
|
||||
root_password_confirm: String::new(),
|
||||
network_wifi_password: String::new(),
|
||||
@@ -251,17 +293,7 @@ impl Default for GlobalState {
|
||||
selected_package_groups: Vec::new(),
|
||||
display_manager: "sddm".to_string(),
|
||||
autologin: true,
|
||||
desktop_environment: {
|
||||
let de = std::env::var("XDG_CURRENT_DESKTOP")
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_lowercase();
|
||||
if de.is_empty() {
|
||||
"plasma".to_string()
|
||||
} else {
|
||||
de
|
||||
}
|
||||
},
|
||||
desktop_environment: detect_desktop_environment(),
|
||||
branding_icons: std::collections::HashMap::new(),
|
||||
branding_pisi_dark: String::new(),
|
||||
branding_pisi_light: String::new(),
|
||||
|
||||
+33
-6
@@ -167,7 +167,8 @@ impl Job for MountBindJob {
|
||||
("/run", format!("{}/run", mt)), // COMAR/dbus için gerekli
|
||||
];
|
||||
for (src, target) in &bind_dirs {
|
||||
run_cmd("mkdir", &["-p", target]).await?;
|
||||
tokio::fs::create_dir_all(target).await
|
||||
.map_err(|e| format!("{} oluşturulamadı: {}", target, e))?;
|
||||
ui.log(format!("mount --bind {} {}", src, target));
|
||||
run_cmd("mount", &["--bind", src, target]).await?;
|
||||
}
|
||||
@@ -175,7 +176,8 @@ impl Job for MountBindJob {
|
||||
// efivarfs — UEFI kurulumunda grub-install için zorunlu
|
||||
let efivarfs_target = format!("{}/sys/firmware/efi/efivars", mt);
|
||||
if std::path::Path::new("/sys/firmware/efi/efivars").exists() {
|
||||
run_cmd("mkdir", &["-p", &efivarfs_target]).await?;
|
||||
tokio::fs::create_dir_all(&efivarfs_target).await
|
||||
.map_err(|e| format!("{} oluşturulamadı: {}", efivarfs_target, e))?;
|
||||
ui.log(format!("mount --bind /sys/firmware/efi/efivars {}", efivarfs_target));
|
||||
run_cmd("mount", &["--bind", "/sys/firmware/efi/efivars", &efivarfs_target]).await?;
|
||||
}
|
||||
@@ -308,6 +310,12 @@ impl Job for InstallGrubJob {
|
||||
fn name(&self) -> &str { "job_bootloader" }
|
||||
|
||||
async fn run(&self, ui: &UiSender) -> Result<(), String> {
|
||||
// Ana dizinleri oluştur (demo modunda rsync çalışmaz)
|
||||
let etc_default = format!("{}/etc/default", self.mount);
|
||||
let _ = tokio::fs::create_dir_all(&etc_default).await;
|
||||
let grub_d = format!("{}/etc/grub.d", self.mount);
|
||||
let _ = tokio::fs::create_dir_all(&grub_d).await;
|
||||
|
||||
// GRUB şifresi (varsa) — bcrypt hash ile /etc/grub.d/40_custom yazılır
|
||||
if !self.password.is_empty() {
|
||||
ui.log("GRUB şifresi yapılandırılıyor…");
|
||||
@@ -776,7 +784,8 @@ impl Job for MountPartitionsJob {
|
||||
if self.plan.efi_mb > 0 {
|
||||
let efi_dir = format!("{}/boot/efi", mount);
|
||||
ui.log(format!("mkdir -p {}", efi_dir));
|
||||
run_cmd("mkdir", &["-p", &efi_dir]).await?;
|
||||
tokio::fs::create_dir_all(&efi_dir).await
|
||||
.map_err(|e| format!("{} oluşturulamadı: {}", efi_dir, e))?;
|
||||
let efi_part = part_path(disk, 1);
|
||||
ui.log(format!("mount {} {}", efi_part, efi_dir));
|
||||
run_cmd("mount", &[&efi_part, &efi_dir]).await?;
|
||||
@@ -1175,6 +1184,9 @@ impl Job for ConfigureHostnameJob {
|
||||
fn name(&self) -> &str { "job_hostname" }
|
||||
|
||||
async fn run(&self, ui: &UiSender) -> Result<(), String> {
|
||||
let etc_dir = format!("{}/etc", self.mount);
|
||||
let _ = tokio::fs::create_dir_all(&etc_dir).await;
|
||||
|
||||
// /etc/hostname
|
||||
let hostname_path = format!("{}/etc/hostname", self.mount);
|
||||
ui.log(format!("Yazılıyor: {}", hostname_path));
|
||||
@@ -1210,6 +1222,10 @@ impl Job for ConfigureLocaleJob {
|
||||
fn name(&self) -> &str { "job_locale" }
|
||||
|
||||
async fn run(&self, ui: &UiSender) -> Result<(), String> {
|
||||
let etc_dir = format!("{}/etc", self.mount);
|
||||
let _ = tokio::fs::create_dir_all(&etc_dir).await;
|
||||
let _ = tokio::fs::create_dir_all(format!("{}/etc/env.d", self.mount)).await;
|
||||
|
||||
// /etc/locale.conf (systemd-style)
|
||||
let conf_path = format!("{}/etc/locale.conf", self.mount);
|
||||
let conf = format!(
|
||||
@@ -1361,7 +1377,8 @@ impl Job for ConfigureKeyboardJob {
|
||||
// /etc/X11/xorg.conf.d/00-keyboard.conf (X11 için)
|
||||
let x11_dir = format!("{}/etc/X11/xorg.conf.d", self.mount);
|
||||
let x11_path = format!("{}/00-keyboard.conf", x11_dir);
|
||||
run_cmd("mkdir", &["-p", &x11_dir]).await?;
|
||||
tokio::fs::create_dir_all(&x11_dir).await
|
||||
.map_err(|e| format!("{} oluşturulamadı: {}", x11_dir, e))?;
|
||||
|
||||
let options_line = if self.variant.is_empty() {
|
||||
String::new()
|
||||
@@ -1450,7 +1467,10 @@ impl Job for CreateUserJob {
|
||||
}
|
||||
|
||||
// sudoers: wheel grubu sudo yetkisi alsın
|
||||
let sudoers_path = format!("{}/etc/sudoers.d/wheel", self.mount);
|
||||
let sudoers_dir = format!("{}/etc/sudoers.d", self.mount);
|
||||
tokio::fs::create_dir_all(&sudoers_dir).await
|
||||
.map_err(|e| format!("{} oluşturulamadı: {}", sudoers_dir, e))?;
|
||||
let sudoers_path = format!("{}/wheel", sudoers_dir);
|
||||
ui.log(format!("Yazılıyor: {}", sudoers_path));
|
||||
tokio::fs::write(&sudoers_path, "%wheel ALL=(ALL:ALL) ALL\n")
|
||||
.await
|
||||
@@ -1510,6 +1530,9 @@ impl Job for GenerateFstabJob {
|
||||
fn name(&self) -> &str { "job_fstab" }
|
||||
|
||||
async fn run(&self, ui: &UiSender) -> Result<(), String> {
|
||||
let etc_dir = format!("{}/etc", self.mount);
|
||||
let _ = tokio::fs::create_dir_all(&etc_dir).await;
|
||||
|
||||
let fstab_path = format!("{}/etc/fstab", self.mount);
|
||||
ui.log(format!("Yazılıyor: {}", fstab_path));
|
||||
|
||||
@@ -1872,7 +1895,8 @@ impl Job for MountCustomPartitionsJob {
|
||||
for item in mount_items {
|
||||
let target_path = format!("{}{}", self.mount, item.mountpoint);
|
||||
ui.log(format!("Bağlama noktası dizini oluşturuluyor: {}", target_path));
|
||||
run_cmd("mkdir", &["-p", &target_path]).await?;
|
||||
tokio::fs::create_dir_all(&target_path).await
|
||||
.map_err(|e| format!("{} oluşturulamadı: {}", target_path, e))?;
|
||||
ui.log(format!("Bağlanıyor: {} ➔ {}", item.device, target_path));
|
||||
run_cmd("mount", &[&item.device, &target_path]).await?;
|
||||
}
|
||||
@@ -1893,6 +1917,9 @@ impl Job for FstabFromCustomPartitionsJob {
|
||||
fn name(&self) -> &str { "job_fstab" }
|
||||
|
||||
async fn run(&self, ui: &UiSender) -> Result<(), String> {
|
||||
let etc_dir = format!("{}/etc", self.mount);
|
||||
let _ = tokio::fs::create_dir_all(&etc_dir).await;
|
||||
|
||||
let fstab_path = format!("{}/etc/fstab", self.mount);
|
||||
ui.log(format!("Yazılıyor: {}", fstab_path));
|
||||
|
||||
|
||||
+1
-2
@@ -6,7 +6,7 @@ mod autoinstall;
|
||||
mod branding;
|
||||
|
||||
use installer::{GlobalState, InstallerStep, WelcomeStep, LocationStep};
|
||||
use steps::{KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, LicenseStep, NetworkStep, DisplayManagerStep};
|
||||
use steps::{KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, NetworkStep, DisplayManagerStep};
|
||||
use eframe::egui;
|
||||
use rust_i18n::t;
|
||||
|
||||
@@ -219,7 +219,6 @@ impl Default for YaliApp {
|
||||
steps: vec![
|
||||
Box::new(WelcomeStep::default()),
|
||||
Box::new(LocationStep::default()),
|
||||
Box::new(LicenseStep),
|
||||
Box::new(KeyboardStep::default()),
|
||||
Box::new(NetworkStep::default()),
|
||||
Box::new(PartitionStep::default()),
|
||||
|
||||
@@ -3,6 +3,7 @@ use rust_i18n::t;
|
||||
use crate::installer::{GlobalState, InstallerStep};
|
||||
|
||||
const DM_OPTIONS: &[&str] = &["sddm", "lightdm", "gdm"];
|
||||
const DE_OPTIONS: &[&str] = &["plasma", "xfce", "gnome", "cinnamon", "budgie", "lxde", "lxqt", "mate", "lumina"];
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DisplayManagerStep;
|
||||
@@ -13,14 +14,9 @@ impl InstallerStep for DisplayManagerStep {
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, state: &mut GlobalState) {
|
||||
let desktop = std::env::var("XDG_CURRENT_DESKTOP")
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_lowercase();
|
||||
let desktop = crate::installer::detect_desktop_environment();
|
||||
if !desktop.is_empty() {
|
||||
state.desktop_environment = desktop;
|
||||
} else if state.desktop_environment.is_empty() {
|
||||
state.desktop_environment = "plasma".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,13 +44,24 @@ impl InstallerStep for DisplayManagerStep {
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
// Masaüstü ortamı (otomatik algılanır)
|
||||
// Masaüstü ortamı (seçilebilir)
|
||||
ui.label(t!("display_manager_de"));
|
||||
if !state.desktop_environment.is_empty() && state.desktop_environment != "default" {
|
||||
ui.colored_label(egui::Color32::from_rgb(120, 180, 120), &state.desktop_environment);
|
||||
let current_de = if state.desktop_environment.is_empty() || state.desktop_environment == "default" {
|
||||
"plasma"
|
||||
} else {
|
||||
ui.colored_label(egui::Color32::from_rgb(200, 160, 40), t!("display_manager_de_not_found"));
|
||||
}
|
||||
state.desktop_environment.as_str()
|
||||
};
|
||||
egui::ComboBox::from_id_source("de_combo")
|
||||
.selected_text(current_de)
|
||||
.show_ui(ui, |ui| {
|
||||
for de in DE_OPTIONS {
|
||||
ui.selectable_value(
|
||||
&mut state.desktop_environment,
|
||||
de.to_string(),
|
||||
*de,
|
||||
);
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
// Otomatik oturum açma
|
||||
|
||||
@@ -5,7 +5,6 @@ pub mod execution;
|
||||
pub mod finish;
|
||||
pub mod partition;
|
||||
pub mod bootloader;
|
||||
pub mod license;
|
||||
pub mod network;
|
||||
pub mod rescue;
|
||||
pub mod display_manager;
|
||||
@@ -18,7 +17,6 @@ pub use execution::ExecutionStep;
|
||||
pub use finish::FinishStep;
|
||||
pub use partition::PartitionStep;
|
||||
pub use bootloader::BootloaderStep;
|
||||
pub use license::LicenseStep;
|
||||
pub use network::NetworkStep;
|
||||
pub use rescue::RescueStep;
|
||||
pub use display_manager::DisplayManagerStep;
|
||||
|
||||
+56
-10
@@ -172,10 +172,12 @@ use crate::installer::{
|
||||
};
|
||||
|
||||
const MIN_DISK_GB: u64 = 10;
|
||||
const EFI_MB: u64 = 512;
|
||||
const EFI_MB: u64 = 300;
|
||||
const SWAP_MB_MIN: u64 = 512;
|
||||
const SWAP_MB_MAX: u64 = 8 * 1024;
|
||||
|
||||
const COMMON_MOUNTPOINTS: &[&str] = &["/", "/boot", "/boot/efi", "/home", "/var", "/tmp", "swap"];
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Manuel bölümleme için yerel durum
|
||||
// ─────────────────────────────────────────────
|
||||
@@ -597,8 +599,9 @@ impl PartitionStep {
|
||||
.spacing([12.0, 8.0])
|
||||
.show(ui, |ui| {
|
||||
|
||||
// Dosya sistemi
|
||||
// Dosya sistemi (boyut önerisi ile)
|
||||
ui.label(t!("mp_fstype"));
|
||||
let prev_fs = self.manual.add_fstype;
|
||||
egui::ComboBox::from_id_source("fs_combo")
|
||||
.selected_text(FS_OPTIONS.get(self.manual.add_fstype).map(|x| x.0).unwrap_or("?"))
|
||||
.show_ui(ui, |ui| {
|
||||
@@ -608,6 +611,15 @@ impl PartitionStep {
|
||||
);
|
||||
}
|
||||
});
|
||||
// FS tipi değişince akıllı boyut öner
|
||||
if prev_fs != self.manual.add_fstype && self.manual.add_size_str.is_empty() {
|
||||
let fs = FS_OPTIONS.get(self.manual.add_fstype).map(|x| &x.1);
|
||||
match fs {
|
||||
Some(FsType::Fat32) => self.manual.add_size_str = "300".to_string(),
|
||||
Some(FsType::Swap) => self.manual.add_size_str = format!("{}", recommended_swap_mb()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
ui.end_row();
|
||||
|
||||
// Boyut
|
||||
@@ -615,11 +627,28 @@ impl PartitionStep {
|
||||
ui.text_edit_singleline(&mut self.manual.add_size_str);
|
||||
ui.end_row();
|
||||
|
||||
// Bağlama noktası
|
||||
// Bağlama noktası (2 aşamalı: combo + input)
|
||||
ui.label(t!("mp_mountpoint"));
|
||||
let mp_field = egui::TextEdit::singleline(&mut self.manual.add_mountpoint)
|
||||
.hint_text(t!("mp_add_dialog_hint"));
|
||||
ui.add(mp_field);
|
||||
ui.horizontal(|ui| {
|
||||
let current_mp = if self.manual.add_mountpoint.is_empty() {
|
||||
t!("mp_select_hint").to_string()
|
||||
} else {
|
||||
self.manual.add_mountpoint.clone()
|
||||
};
|
||||
egui::ComboBox::from_id_source("mp_combo")
|
||||
.selected_text(¤t_mp)
|
||||
.width(120.0)
|
||||
.show_ui(ui, |ui| {
|
||||
for mp in COMMON_MOUNTPOINTS {
|
||||
if ui.selectable_label(false, *mp).clicked() {
|
||||
self.manual.add_mountpoint = mp.to_string();
|
||||
}
|
||||
}
|
||||
});
|
||||
let mp_field = egui::TextEdit::singleline(&mut self.manual.add_mountpoint)
|
||||
.hint_text(t!("mp_add_dialog_hint"));
|
||||
ui.add(mp_field);
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
// LUKS şifreleme
|
||||
@@ -858,11 +887,28 @@ impl PartitionStep {
|
||||
ui.text_edit_singleline(&mut self.manual.add_lv_size_str);
|
||||
ui.end_row();
|
||||
|
||||
// Bağlama noktası
|
||||
// Bağlama noktası (2 aşamalı: combo + input)
|
||||
ui.label(t!("mp_mountpoint"));
|
||||
let mp_field = egui::TextEdit::singleline(&mut self.manual.add_lv_mountpoint)
|
||||
.hint_text(t!("mp_add_dialog_hint"));
|
||||
ui.add(mp_field);
|
||||
ui.horizontal(|ui| {
|
||||
let current_mp = if self.manual.add_lv_mountpoint.is_empty() {
|
||||
t!("mp_select_hint").to_string()
|
||||
} else {
|
||||
self.manual.add_lv_mountpoint.clone()
|
||||
};
|
||||
egui::ComboBox::from_id_source("lv_mp_combo")
|
||||
.selected_text(¤t_mp)
|
||||
.width(120.0)
|
||||
.show_ui(ui, |ui| {
|
||||
for mp in COMMON_MOUNTPOINTS {
|
||||
if ui.selectable_label(false, *mp).clicked() {
|
||||
self.manual.add_lv_mountpoint = mp.to_string();
|
||||
}
|
||||
}
|
||||
});
|
||||
let mp_field = egui::TextEdit::singleline(&mut self.manual.add_lv_mountpoint)
|
||||
.hint_text(t!("mp_add_dialog_hint"));
|
||||
ui.add(mp_field);
|
||||
});
|
||||
ui.end_row();
|
||||
});
|
||||
|
||||
|
||||
+72
-20
@@ -23,6 +23,33 @@ impl Default for UsersStep {
|
||||
}
|
||||
}
|
||||
|
||||
/// Gerçek addan geçerli bir Linux kullanıcı adı türetir.
|
||||
/// - Küçük harfe çevirir
|
||||
/// - Türkçe karakterleri ASCII'ye dönüştürür (ş→s, ğ→g, ı→i, ö→o, ü→u, ç→c)
|
||||
/// - Boşlukları `-`'ye çevirir
|
||||
/// - Geçersiz karakterleri temizler
|
||||
fn derive_username(real_name: &str) -> String {
|
||||
let mut name = real_name.to_lowercase();
|
||||
name = name
|
||||
.replace('ş', "s")
|
||||
.replace('ğ', "g")
|
||||
.replace('ı', "i")
|
||||
.replace('ö', "o")
|
||||
.replace('ü', "u")
|
||||
.replace('ç', "c")
|
||||
.replace(' ', "-")
|
||||
.replace('_', "-");
|
||||
let valid: String = name.chars().filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '_').collect();
|
||||
let trimmed = valid.trim_matches('-').to_string();
|
||||
if trimmed.is_empty() {
|
||||
"user".to_string()
|
||||
} else if trimmed.len() > 32 {
|
||||
trimmed[..32].trim_end_matches('-').to_string()
|
||||
} else {
|
||||
trimmed
|
||||
}
|
||||
}
|
||||
|
||||
/// Kullanıcı adının geçerliliğini kontrol eder.
|
||||
/// Linux kuralları: küçük harf veya alt çizgi ile başlamalı,
|
||||
/// en fazla 32 karakter, yalnızca [a-z0-9_-] içermeli.
|
||||
@@ -75,7 +102,21 @@ impl InstallerStep for UsersStep {
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, state: &mut GlobalState) {
|
||||
// Hostname boşsa username'den öner
|
||||
// Gerçek ad varsa ama kullanıcı adı yoksa türet
|
||||
if !state.real_name.is_empty() && state.username.is_empty() {
|
||||
state.username = derive_username(&state.real_name);
|
||||
}
|
||||
// Hostname boşsa önce /etc/hostname dene
|
||||
if state.hostname.is_empty() {
|
||||
let sys_hostname = std::fs::read_to_string("/etc/hostname")
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
if !sys_hostname.is_empty() && sys_hostname != "localhost" {
|
||||
state.hostname = sys_hostname;
|
||||
}
|
||||
}
|
||||
// Hâlâ boşsa ve username varsa username-pc öner
|
||||
if state.hostname.is_empty() && !state.username.is_empty() {
|
||||
state.hostname = format!("{}-pc", state.username);
|
||||
}
|
||||
@@ -99,23 +140,37 @@ impl InstallerStep for UsersStep {
|
||||
.inner_margin(egui::Margin::same(12.0))
|
||||
.show(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
egui::Grid::new("users_grid")
|
||||
.num_columns(2)
|
||||
.spacing([12.0, 10.0])
|
||||
.show(ui, |ui| {
|
||||
// Kullanıcı adı
|
||||
ui.label(egui::RichText::new(t!("username_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
ui.text_edit_singleline(&mut state.username);
|
||||
if !state.username.is_empty() {
|
||||
if is_valid_username(&state.username) {
|
||||
ui.colored_label(theme::c_success(), "✓");
|
||||
} else {
|
||||
ui.colored_label(theme::c_error(), "✗");
|
||||
egui::Grid::new("users_grid")
|
||||
.num_columns(2)
|
||||
.spacing([12.0, 10.0])
|
||||
.show(ui, |ui| {
|
||||
// Gerçek ad
|
||||
ui.label(egui::RichText::new(t!("real_name_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
let prev = state.real_name.clone();
|
||||
ui.text_edit_singleline(&mut state.real_name);
|
||||
if state.real_name != prev && !state.real_name.is_empty() {
|
||||
state.username = derive_username(&state.real_name);
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
if !state.real_name.is_empty() {
|
||||
ui.colored_label(theme::c_success(), "✓");
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
// Kullanıcı adı (otomatik türetilir, düzenlenebilir)
|
||||
ui.label(egui::RichText::new(t!("username_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
ui.text_edit_singleline(&mut state.username);
|
||||
if !state.username.is_empty() {
|
||||
if is_valid_username(&state.username) {
|
||||
ui.colored_label(theme::c_success(), "✓");
|
||||
} else {
|
||||
ui.colored_label(theme::c_error(), "✗");
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
// Şifre
|
||||
ui.label(egui::RichText::new(t!("password_label")).strong().color(theme::c_text()));
|
||||
@@ -159,9 +214,6 @@ impl InstallerStep for UsersStep {
|
||||
// Hostname
|
||||
ui.label(egui::RichText::new(t!("hostname_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
if state.hostname.is_empty() && !state.username.is_empty() {
|
||||
state.hostname = format!("{}-pc", state.username);
|
||||
}
|
||||
ui.text_edit_singleline(&mut state.hostname);
|
||||
if !state.hostname.is_empty() {
|
||||
if is_valid_hostname(&state.hostname) {
|
||||
|
||||
Reference in New Issue
Block a user