forked from pisilinux-rs/yali-rs
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f35d0180b | |||
| c2ef2e23fc | |||
| 09ffa5346d |
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Licensed under the GNU General Public License, version 3.
|
||||
# See the file http://www.gnu.org/licenses/gpl.txt
|
||||
|
||||
from pisi.actionsapi import pisitools
|
||||
from pisi.actionsapi import shelltools
|
||||
|
||||
def setup():
|
||||
shelltools.system("cargo fetch --locked")
|
||||
|
||||
def build():
|
||||
shelltools.system("cargo build --release --frozen")
|
||||
|
||||
def install():
|
||||
pisitools.dobin("target/release/yali-rs")
|
||||
|
||||
pisitools.insinto("/usr/share/yali", "branding*.toml")
|
||||
pisitools.insinto("/usr/share/yali/locales", "locales/*.toml")
|
||||
pisitools.insinto("/usr/share/yali/assets", "assets/*")
|
||||
|
||||
pisitools.insinto("/usr/share/applications", "assets/yali.desktop")
|
||||
pisitools.insinto("/usr/share/pixmaps", "assets/yali.png")
|
||||
+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"
|
||||
|
||||
@@ -25,8 +25,14 @@ keyboard = "Keyboard"
|
||||
keyboard_title = "Keyboard Layout"
|
||||
keyboard_description = "Select your keyboard layout and test it below."
|
||||
keyboard_layout_label = "Layout"
|
||||
keyboard_variant_label = "Variant"
|
||||
keyboard_layout_search_hint = "Search layouts..."
|
||||
keyboard_layout_search_no_match = "No layouts match"
|
||||
keyboard_variant_search_hint = "Search variants..."
|
||||
keyboard_variant_search_no_match = "No variants match"
|
||||
keyboard_test_label = "Test Area"
|
||||
keyboard_test_hint = "Type here to test your keyboard…"
|
||||
keyboard_variant_default = "Default"
|
||||
keyboard_selected = "Selected"
|
||||
|
||||
# ── Partition ─────────────────────────────────────────────
|
||||
@@ -45,6 +51,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 +161,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 +259,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"
|
||||
|
||||
+13
-1
@@ -25,8 +25,14 @@ keyboard = "Klavye"
|
||||
keyboard_title = "Klavye Düzeni"
|
||||
keyboard_description = "Kullanmak istediğiniz klavye düzenini seçin ve aşağıdan test edin."
|
||||
keyboard_layout_label = "Düzen"
|
||||
keyboard_variant_label = "Varyant"
|
||||
keyboard_layout_search_hint = "Düzenlerde ara..."
|
||||
keyboard_layout_search_no_match = "Eşleşen düzen yok"
|
||||
keyboard_variant_search_hint = "Varyantlarda ara..."
|
||||
keyboard_variant_search_no_match = "Eşleşen varyant yok"
|
||||
keyboard_test_label = "Test Alanı"
|
||||
keyboard_test_hint = "Buraya yazarak klavyenizi test edin…"
|
||||
keyboard_variant_default = "Varsayılan"
|
||||
keyboard_selected = "Seçili"
|
||||
|
||||
# ── Partition ─────────────────────────────────────────────
|
||||
@@ -45,6 +51,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 +93,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 +138,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 +231,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"
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" ?>
|
||||
<!DOCTYPE PISI SYSTEM "https://pisilinux.org/projeler/pisi/pisi-spec.dtd">
|
||||
<PISI>
|
||||
<Source>
|
||||
<Name>yali-rs</Name>
|
||||
<Homepage>https://gitlab.com/erkanisik/yali-rs</Homepage>
|
||||
<Packager>
|
||||
<Name>Erkan Işık</Name>
|
||||
<Email>erkanisik@pisilinux.org</Email>
|
||||
</Packager>
|
||||
<License>GPLv2</License>
|
||||
<IsA>app:gui</IsA>
|
||||
<Summary xml:lang="en">Yet Another Linux Installer</Summary>
|
||||
<Summary xml:lang="tr">PiSi Linux için Grafik Kurulum Programı</Summary>
|
||||
|
||||
<Description xml:lang="en">YALI is the graphical installer for PiSi Linux distribution which is written using Rust and egui framework.</Description>
|
||||
<Description xml:lang="tr">YALI, Rust ve egui framework kullanılarak yazılmış olan PiSi Linux dağıtımının grafik kurulum programıdır.</Description>
|
||||
|
||||
<Archive sha1sum="" type="targz">
|
||||
https://gitlab.com/erkanisik/yali-rs/-/archive/v4.0.0/yali-rs-v4.0.0.tar.gz
|
||||
</Archive>
|
||||
|
||||
<BuildDependencies>
|
||||
<Dependency>rustc</Dependency>
|
||||
<Dependency>cargo</Dependency>
|
||||
<Dependency>make</Dependency>
|
||||
</BuildDependencies>
|
||||
</Source>
|
||||
|
||||
<Package>
|
||||
<Name>yali-rs</Name>
|
||||
<RuntimeDependencies>
|
||||
<Dependency>dbus</Dependency>
|
||||
</RuntimeDependencies>
|
||||
<Conflicts>
|
||||
<Package>yali</Package>
|
||||
</Conflicts>
|
||||
<Files>
|
||||
<Path fileType="executable">/usr/bin/yali-rs</Path>
|
||||
<Path fileType="data">/usr/share/yali</Path>
|
||||
<Path fileType="data">/usr/share/applications/yali.desktop</Path>
|
||||
<Path fileType="data">/usr/share/pixmaps/yali.png</Path>
|
||||
</Files>
|
||||
</Package>
|
||||
|
||||
<History>
|
||||
<Update release="1">
|
||||
<Date>2026-06-01</Date>
|
||||
<Version>4.0.0</Version>
|
||||
<Comment>First release.</Comment>
|
||||
<Name>Erkan IŞIK</Name>
|
||||
<Email>erkanisik@pisilinux.org</Email>
|
||||
</Update>
|
||||
</History>
|
||||
</PISI>
|
||||
@@ -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(),
|
||||
|
||||
+37
-10
@@ -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!(
|
||||
@@ -1229,7 +1245,7 @@ impl Job for ConfigureLocaleJob {
|
||||
let _ = tokio::fs::write(&pisi_locale_path, pisi_locale).await;
|
||||
|
||||
// /etc/locale.gen — satırın başındaki # kaldırılır
|
||||
let gen_path = format!("{}/etc/locale.gen", self.mount);
|
||||
let gen_path = format!("{}/etc/locale-gen", self.mount);
|
||||
ui.log(format!("Güncelleniyor: {}", gen_path));
|
||||
let content = tokio::fs::read_to_string(&gen_path).await.unwrap_or_default();
|
||||
let updated = content
|
||||
@@ -1246,11 +1262,11 @@ impl Job for ConfigureLocaleJob {
|
||||
.join("\n");
|
||||
tokio::fs::write(&gen_path, updated + "\n")
|
||||
.await
|
||||
.map_err(|e| format!("/etc/locale.gen yazılamadı: {}", e))?;
|
||||
.map_err(|e| format!("/etc/locale-gen yazılamadı: {}", e))?;
|
||||
|
||||
// locale-gen çalıştır
|
||||
ui.log("locale-gen çalıştırılıyor…");
|
||||
run_chroot(&self.mount, &["locale-gen"]).await?;
|
||||
ui.log("locale.gen çalıştırılıyor…");
|
||||
run_chroot(&self.mount, &["locale.gen"]).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -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
|
||||
|
||||
+239
-52
@@ -1,29 +1,138 @@
|
||||
use eframe::egui;
|
||||
use rust_i18n::t;
|
||||
use crate::installer::{GlobalState, InstallerStep};
|
||||
use crate::ui::theme;
|
||||
|
||||
/// Klavye düzeni ve varyantı seçimi.
|
||||
/// Gerçek uygulamada mevcut düzenler `localectl list-keymaps`
|
||||
/// veya `/usr/share/X11/xkb/rules/evdev.lst` dosyasından okunabilir.
|
||||
/// Mevcut düzenler `/usr/share/X11/xkb/rules/evdev.lst` dosyasından okunur.
|
||||
/// Dosya okunamazsa sabit bir temel liste kullanılır.
|
||||
pub struct KeyboardStep {
|
||||
test_input: String,
|
||||
layouts: Vec<(&'static str, &'static str)>, // (kod, gösterim adı)
|
||||
layout_filter: String,
|
||||
variant_filter: String,
|
||||
layouts: Vec<(String, String)>, // (kod, gösterim adı)
|
||||
variants: Vec<(String, String, String)>, // (layout, variant, gösterim adı)
|
||||
}
|
||||
|
||||
fn default_keyboard_layouts() -> Vec<(String, String)> {
|
||||
vec![
|
||||
("tr".to_string(), "Türkçe (Q)".to_string()),
|
||||
("us".to_string(), "İngilizce (US)".to_string()),
|
||||
("de".to_string(), "Almanca".to_string()),
|
||||
("fr".to_string(), "Fransızca".to_string()),
|
||||
("es".to_string(), "İspanyolca".to_string()),
|
||||
("ru".to_string(), "Rusça".to_string()),
|
||||
("ar".to_string(), "Arapça".to_string()),
|
||||
]
|
||||
}
|
||||
|
||||
fn default_keyboard_variants() -> Vec<(String, String, String)> {
|
||||
vec![
|
||||
("tr".to_string(), "f".to_string(), "Türkçe (F)".to_string()),
|
||||
]
|
||||
}
|
||||
|
||||
fn load_keyboard_layouts() -> (Vec<(String, String)>, Vec<(String, String, String)>) {
|
||||
let path = "/usr/share/X11/xkb/rules/evdev.lst";
|
||||
let contents = std::fs::read_to_string(path).ok();
|
||||
|
||||
let mut layouts = Vec::new();
|
||||
let mut variants = Vec::new();
|
||||
let mut section: Option<&str> = None;
|
||||
|
||||
if let Some(text) = contents {
|
||||
for line in text.lines() {
|
||||
let line = line.trim_end();
|
||||
if line.starts_with('!') {
|
||||
section = if line.starts_with("! layout") {
|
||||
Some("layout")
|
||||
} else if line.starts_with("! variant") {
|
||||
Some("variant")
|
||||
} else {
|
||||
None
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
let trimmed = line.trim();
|
||||
if trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(section) = section {
|
||||
if let Some((code, desc)) = split_code_desc(trimmed) {
|
||||
match section {
|
||||
"layout" => layouts.push((code.to_string(), desc.to_string())),
|
||||
"variant" => {
|
||||
if let Some((layout, display)) = desc.split_once(':') {
|
||||
variants.push((layout.trim().to_string(), code.to_string(), display.trim().to_string()));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if layouts.is_empty() {
|
||||
(default_keyboard_layouts(), default_keyboard_variants())
|
||||
} else {
|
||||
(layouts, variants)
|
||||
}
|
||||
}
|
||||
|
||||
fn split_code_desc(line: &str) -> Option<(&str, &str)> {
|
||||
let mut parts = line.splitn(2, char::is_whitespace);
|
||||
let code = parts.next()?.trim();
|
||||
let desc = parts.next()?.trim();
|
||||
if code.is_empty() || desc.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((code, desc))
|
||||
}
|
||||
}
|
||||
fn layout_display_name(layout: &str, layouts: &[(String, String)]) -> String {
|
||||
layouts
|
||||
.iter()
|
||||
.find(|(code, _)| code == layout)
|
||||
.map(|(_, display)| display.clone())
|
||||
.unwrap_or_else(|| layout.to_string())
|
||||
}
|
||||
|
||||
fn variant_display_name(
|
||||
layout: &str,
|
||||
variant: &str,
|
||||
variants: &[(String, String, String)],
|
||||
) -> String {
|
||||
variants
|
||||
.iter()
|
||||
.find(|(layout_code, v_code, _)| layout_code == layout && v_code == variant)
|
||||
.map(|(_, _, display)| display.clone())
|
||||
.unwrap_or_else(|| variant.to_string())
|
||||
}
|
||||
|
||||
|
||||
fn apply_system_keyboard_layout(layout: &str, variant: &str) {
|
||||
if std::env::var("DISPLAY").is_ok() {
|
||||
let mut cmd = std::process::Command::new("setxkbmap");
|
||||
cmd.arg(layout);
|
||||
if !variant.is_empty() {
|
||||
cmd.arg("-variant").arg(variant);
|
||||
}
|
||||
let _ = cmd.output();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for KeyboardStep {
|
||||
fn default() -> Self {
|
||||
let (layouts, variants) = load_keyboard_layouts();
|
||||
Self {
|
||||
test_input: String::new(),
|
||||
layouts: vec![
|
||||
("tr", "Türkçe (Q)"),
|
||||
("tr:f", "Türkçe (F)"),
|
||||
("us", "İngilizce (US)"),
|
||||
("de", "Almanca"),
|
||||
("fr", "Fransızca"),
|
||||
("es", "İspanyolca"),
|
||||
("ru", "Rusça"),
|
||||
("ar", "Arapça"),
|
||||
],
|
||||
layout_filter: String::new(),
|
||||
variant_filter: String::new(),
|
||||
layouts,
|
||||
variants,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +151,7 @@ impl InstallerStep for KeyboardStep {
|
||||
"us".to_string()
|
||||
};
|
||||
}
|
||||
apply_system_keyboard_layout(&state.keyboard_layout, &state.keyboard_variant);
|
||||
}
|
||||
|
||||
fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) {
|
||||
@@ -50,51 +160,128 @@ impl InstallerStep for KeyboardStep {
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
let available_variants: Vec<_> = self
|
||||
.variants
|
||||
.iter()
|
||||
.filter(|(layout, _, _)| layout == &state.keyboard_layout)
|
||||
.collect();
|
||||
|
||||
ui.columns(2, |cols| {
|
||||
// Sol: Düzen listesi
|
||||
cols[0].label(t!("keyboard_layout_label"));
|
||||
egui::ScrollArea::vertical()
|
||||
.id_source("kbd_layouts")
|
||||
.max_height(260.0)
|
||||
.show(&mut cols[0], |ui| {
|
||||
// &(code, display) pattern ile code: &str, display: &str olur
|
||||
for &(code, display) in &self.layouts {
|
||||
// tr:f kodunu layout+variant olarak ayır
|
||||
let (layout, variant) = if let Some(pos) = code.find(':') {
|
||||
(&code[..pos], &code[pos + 1..])
|
||||
} else {
|
||||
(code, "")
|
||||
};
|
||||
cols[0].vertical(|ui| {
|
||||
egui::Frame::group(ui.style())
|
||||
.fill(theme::c_bg_widget())
|
||||
.stroke(egui::Stroke::new(1.0, egui::Color32::from_gray(200)))
|
||||
.rounding(egui::Rounding::same(8.0))
|
||||
.inner_margin(egui::Margin::same(8.0))
|
||||
.show(ui, |ui| {
|
||||
ui.label(t!("keyboard_layout_label"));
|
||||
ui.add(
|
||||
egui::TextEdit::singleline(&mut self.layout_filter)
|
||||
.hint_text(t!("keyboard_layout_search_hint")),
|
||||
);
|
||||
ui.add_space(4.0);
|
||||
|
||||
let is_selected = state.keyboard_layout == layout
|
||||
&& state.keyboard_variant == variant;
|
||||
egui::ScrollArea::vertical()
|
||||
.id_source("kbd_layout_scroll")
|
||||
.auto_shrink([false, false])
|
||||
.max_height(240.0)
|
||||
.show(ui, |ui| {
|
||||
let filter = self.layout_filter.to_lowercase();
|
||||
let filtered_layouts: Vec<_> = self.layouts.iter()
|
||||
.filter(|(code, display)| {
|
||||
filter.is_empty()
|
||||
|| code.to_lowercase().contains(&filter)
|
||||
|| display.to_lowercase().contains(&filter)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if ui.selectable_label(is_selected, display).clicked() {
|
||||
state.keyboard_layout = layout.to_string();
|
||||
state.keyboard_variant = variant.to_string();
|
||||
}
|
||||
}
|
||||
});
|
||||
if filtered_layouts.is_empty() {
|
||||
ui.label(t!("keyboard_layout_search_no_match"));
|
||||
}
|
||||
for (code, display) in filtered_layouts {
|
||||
let is_selected = state.keyboard_layout == *code;
|
||||
if ui.selectable_label(is_selected, display).clicked() {
|
||||
state.keyboard_layout = code.to_string();
|
||||
self.variant_filter.clear();
|
||||
if !self.variants.iter().any(|(layout, _, _)| layout == code) {
|
||||
state.keyboard_variant.clear();
|
||||
}
|
||||
apply_system_keyboard_layout(&state.keyboard_layout, &state.keyboard_variant);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Sağ: Test alanı
|
||||
cols[1].label(t!("keyboard_test_label"));
|
||||
cols[1].add_space(4.0);
|
||||
cols[1].add(
|
||||
egui::TextEdit::multiline(&mut self.test_input)
|
||||
.desired_rows(5)
|
||||
.hint_text(t!("keyboard_test_hint")),
|
||||
);
|
||||
cols[1].vertical(|ui| {
|
||||
egui::Frame::group(ui.style())
|
||||
.fill(theme::c_bg_widget())
|
||||
.stroke(egui::Stroke::new(1.0, egui::Color32::from_gray(200)))
|
||||
.rounding(egui::Rounding::same(8.0))
|
||||
.inner_margin(egui::Margin::same(8.0))
|
||||
.show(ui, |ui| {
|
||||
ui.label(t!("keyboard_variant_label"));
|
||||
ui.add(
|
||||
egui::TextEdit::singleline(&mut self.variant_filter)
|
||||
.hint_text(t!("keyboard_variant_search_hint")),
|
||||
);
|
||||
ui.add_space(4.0);
|
||||
|
||||
cols[1].add_space(8.0);
|
||||
cols[1].label(format!("{}: {}", t!("keyboard_selected"), {
|
||||
let variant_part = if state.keyboard_variant.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" ({})", state.keyboard_variant)
|
||||
};
|
||||
format!("{}{}", state.keyboard_layout, variant_part)
|
||||
}));
|
||||
egui::ScrollArea::vertical()
|
||||
.id_source("kbd_variant_scroll")
|
||||
.auto_shrink([false, false])
|
||||
.max_height(240.0)
|
||||
.show(ui, |ui| {
|
||||
let variant_filter = self.variant_filter.to_lowercase();
|
||||
let filtered_variants: Vec<_> = available_variants
|
||||
.into_iter()
|
||||
.filter(|(_, variant, display)| {
|
||||
variant_filter.is_empty()
|
||||
|| variant.to_lowercase().contains(&variant_filter)
|
||||
|| display.to_lowercase().contains(&variant_filter)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut selected_default = state.keyboard_variant.is_empty();
|
||||
if ui.selectable_value(&mut selected_default, true, t!("keyboard_variant_default")).clicked() {
|
||||
state.keyboard_variant.clear();
|
||||
apply_system_keyboard_layout(&state.keyboard_layout, "");
|
||||
}
|
||||
if filtered_variants.is_empty() {
|
||||
ui.label(t!("keyboard_variant_search_no_match"));
|
||||
}
|
||||
for (_, variant, display) in filtered_variants {
|
||||
let is_selected = state.keyboard_variant == *variant;
|
||||
if ui.selectable_label(is_selected, display).clicked() {
|
||||
state.keyboard_variant = variant.to_string();
|
||||
apply_system_keyboard_layout(&state.keyboard_layout, &state.keyboard_variant);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.label(t!("keyboard_test_label"));
|
||||
ui.add_space(4.0);
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut self.test_input)
|
||||
.desired_rows(5)
|
||||
.desired_width(ui.available_width())
|
||||
.hint_text(t!("keyboard_test_hint")),
|
||||
);
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.label(format!("{}: {}", t!("keyboard_selected"), {
|
||||
let layout_name = layout_display_name(&state.keyboard_layout, &self.layouts);
|
||||
if state.keyboard_variant.is_empty() {
|
||||
layout_name
|
||||
} else {
|
||||
let variant_name = variant_display_name(&state.keyboard_layout, &state.keyboard_variant, &self.variants);
|
||||
format!("{} ({})", layout_name, variant_name)
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
fn is_complete(&self, state: &GlobalState) -> bool {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
+92
-21
@@ -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.
|
||||
@@ -50,6 +77,24 @@ fn is_valid_hostname(s: &str) -> bool {
|
||||
s.chars().all(|c| c.is_ascii_alphanumeric() || c == '-')
|
||||
}
|
||||
|
||||
/// Otomatik hostname üretirken nokta, boşluk ve geçersiz karakterleri kaldırır.
|
||||
fn sanitize_hostname(s: &str) -> String {
|
||||
let mut hostname: String = s
|
||||
.chars()
|
||||
.filter(|c| c.is_ascii_alphanumeric())
|
||||
.collect();
|
||||
|
||||
if hostname.len() > 63 {
|
||||
hostname.truncate(63);
|
||||
}
|
||||
|
||||
if hostname.is_empty() {
|
||||
"host".to_string()
|
||||
} else {
|
||||
hostname
|
||||
}
|
||||
}
|
||||
|
||||
fn password_strength(p: &str) -> (String, egui::Color32) {
|
||||
let len = p.len();
|
||||
let has_upper = p.chars().any(|c| c.is_uppercase());
|
||||
@@ -75,9 +120,24 @@ 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();
|
||||
let sys_hostname = sanitize_hostname(&sys_hostname);
|
||||
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);
|
||||
state.hostname = sanitize_hostname(&format!("{}pc", state.username));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,23 +159,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 +233,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) {
|
||||
|
||||
+1
-1
@@ -176,7 +176,7 @@ fn build_style() -> egui::Style {
|
||||
style.spacing.item_spacing = Vec2::new(8.0, 6.0);
|
||||
style.spacing.button_padding = Vec2::new(12.0, 6.0);
|
||||
style.spacing.indent = 16.0;
|
||||
style.spacing.scroll.bar_width = 8.0;
|
||||
style.spacing.scroll.bar_width = 14.0;
|
||||
|
||||
// Metin boyutları
|
||||
style.text_styles = [
|
||||
|
||||
Reference in New Issue
Block a user