6 Commits

Author SHA1 Message Date
Erkan IŞIK 22de7d5255 Dead code uyarılarını gidermek için #[allow(dead_code)] eklendi
- ManualState struct'ına dead code uyarısı kaldırma eklendi
- FsType enum'ına dead code uyarısı kaldırma eklendi (Lvm variant'ı için)
- VolumeGroup struct'ına dead code uyarısı kaldırma eklendi
- Bu alanlar gelecekte LVM ve gelişmiş bölümleme özellikleri için planlanmış

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-25 18:18:38 +03:00
Erkan IŞIK 75e2b1bf5e Tablo sütun başlıkları için translation key'leri eklendi
- Türkçe ve İngilizce dil dosyalarına tablo sütun başlıkları eklendi
- column_device, column_mount_point, column_label, column_type, column_format, column_size
- Manuel bölümleme tablosunda kullanılan sütun başlıkları için çeviri desteği

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-24 08:41:37 +03:00
Erkan IŞIK dcd3ec4efb Bölümleme tablosu boyut sütunu düzeltmeleri ve disk seçimi UI iyileştirmeleri
- Manuel bölümleme tablosunda boyut sütununun görünmemesi sorunu çözüldü
- Disk başlığı kırmızı renk ve kutucuk simge sorunları düzeltildi (c_accent yerine c_text, Unicode ok karakteri)
- Disk seçimi için ComboBox bileşeni eklendi (eski Grid tabanlı sistem yerine)
- Tablo header hizalamaları düzeltildi (sol/sağ/ortalanmış sütunlar)
- Font yüklemesi iyileştirildi (monospace font desteği ve emoji fallback mekanizması)
- LVM LV boyut kontrolü sembolik boyutlar için düzeltildi
- Disk bölüm numarası formatlama fonksiyonu eklendi (nvme0n1p1 vs sda1 mantığı)

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-24 00:43:32 +03:00
Erkan IŞIK 7209d58ea0 fix: parted birim tuzağı — mkpart argümanları MiB sonekine geçti
- parted "MB" sonekini onalık (10^6) okur, hesaplarımız binary (2^20):
  3944MB → sektör 7703125 (hizasız) → "en yakın konum" hatası.
  Tüm mkpart konumları artık "{}MiB" (tamsayı MiB = 2048 sektör katı).
- Bölümleme Planı şeridi yalnızca seçili disk için gösteriliyor
  (state.selected_disk), tüm diskler aynı anda değil.
- AGENTS.md: parted birim tuzağı notu eklendi.
2026-08-23 14:35:01 +03:00
Erkan IŞIK 0a02f576d4 docs: ajan notları — çoklu disk/granular bölümleme mimarisi ve yapılacaklar 2026-08-22 21:37:36 +03:00
Erkan IŞIK 5c148472c4 feat: çoklu disk desteği ve eski Yali tarzı granular bölümleme işlemleri
- CustomPartition artık disk alanı taşıyor; bölümler diske göre gruplanıyor
- Manuel modda TÜM disklerin mevcut bölümleri otomatik listeleniyor
- Tablo her disk için kendi sütun başlıklarını gösteriyor
- Silinmek işaretli bölümler tek 'Boş Alan' satırında birleşiyor (GParted tarzı);
  Boş Alan = disk − (korunanlar + yeni tanımlar), 0'a düşerse satır gizlenir
- Sil/Geri Al: fiziksel bölümler silinmek İŞARETLENİR (OperationDestroyDevice
  karşılığı), kullanıcı tanımı olanlar listeden tamamen kalkar
- Kurulum job'ları granular modele geçti: mktable ile tüm disk sıfırlanmıyor;
  rm → boş alana mkpart (sysfs geometrisi) → yalnızca istenenler mkfs.
  İşlemi olmayan disklere hiç dokunulmaz
- Slot ataması parted davranışıyla birebir: silinen numara yeniden kullanılır
- 'Kalan alan' sembolik kaldı; boyut kurulum anında boşluktan belirlenir
  (taşma imkânsız) + kapasite aşımı uyarısı/kilidi eklendi
- Özet ekranı ve bölüm planı: disk başına Mevcut/Kurulum Sonrası çift bar,
  kapasite gösterimleri düzeltildi
- Remixicon simge düzeltmesi (hard-drive), mp_* yeni çeviri anahtarları
- Testler: slot yeniden kullanımı, kapasite aşımı, disk-bazlı hesaplar (22/22)
2026-08-22 21:29:31 +03:00
9 changed files with 819 additions and 1373 deletions
+73
View File
@@ -0,0 +1,73 @@
# AGENTS.md — Yali-rs Geliştirme Notları
> Ajan oturumları arasında süreklilik için. Son güncelleme: parted MiB düzeltmesi (23 Ağu)
## Proje
- PisiLinux kurulum aracı (eski Python Yali'nin Rust/egui yeniden yazımı).
- Ana dallar: `main`. Push: `git push origin main`.
- Referans kod: `/media/yedekleme/github/erkanisik/yali` (eski Python Yali —
storage mimarisi: `yali/storage/{operations,partitioning,devicetree}.py`).
## Mimari Kararlar (2026-08 dönemi)
### Çoklu Disk Manuel Bölümleme
- `CustomPartition` alanları (`src/installer.rs`):
- `disk`: bölümün ait olduğu disk ("/dev/sda") — gruplama bununla.
- `orig_device`: lsblk'tan gelen fiziksel cihaz adı; **boş = kullanıcı tanımı yeni bölüm**.
Silme/işaret mantığı buna dayanır.
- `delete: bool`: eski Yali `OperationDestroyDevice` karşılığı → kurulumda `parted rm`.
- UI (`src/steps/partition.rs`):
- Manuel moda girişte TÜM diskler otomatik taranıp listelenir (disk kartına tıklamak
artık sadece "yeni bölüm hedef diski" seçer; tanımları sıfırlamaz — merge eder,
`orig_device` eşleşmesinde kullanıcı tanımı kazanır).
- Tablo `TableRow::Part | TableRow::FreeSpace` enum'u ile kurulur; HER disk grubu
kendi sütun başlıklarını çizer (`draw_partition_table_header`).
- Silinmek işaretli bölümler ayrı satır çizmez; İLK silinenin konumunda TEK
"Boş Alan" satırında birleşir (GParted tarzı).
`Boş Alan = disk (korunan fiziksel + yeni tanımlar)`; 0 ise satır gizlenir.
- "Kalan alan" SEMBOLİKTİR (size_mb=0): kaydetme anında sayıya çevrilmez!
Tabloda/özetde kapasite olarak gösterilir; gerçek boyut kurulum anında boşluktan alınır.
(Somutlaştırma taşmaya yol açtığı için kaldırıldı — swap sonradan büyütülünce kök patlıyordu.)
- Slot ataması (`installer::assign_partition_numbers`): parted'ın gerçek davranışını
simüle eder — fiziksel (silinmeyen) bölümler numarasını korur, silinenlerin slotu
boşalır, yeni tanımlar en küçük boş slotu alır. UI ile job tutarlılığı buradan gelir.
### Granular Kurulum Job'ı (`jobs/partition.rs::CustomPartitionJob`)
Eski wipe&rebuild (mktable + hepsini baştan yarat) KALDIRILDI. Yeni fazlar:
1. FAZ A: `delete=true` olanlar `parted rm N` — N, `orig_device`'tan;
**büyükten küçüğe** sil (kernel slot kayması).
2. FAZ B: yeni tanımlar `disk_free_regions_mb()` ile sysfs'ten hesaplanan boş
bölgelere first-fit yerleşir ("kalan" = en büyük bölge). Sabit boyutlular ÖNCE,
sembolikler SONRA (`creates.sort_by_key(size==0)`) işlenir.
3. FAZ C: yalnızca istenenler mkfs (LUKS akışı korundu).
- İşlemi olmayan disklere ve `delete=true` satırlara mount/fstab/summary dokunmaz.
### Diğer Düzeltmeler
- **parted birim tuzağı (KRİTİK):** parted `"MB"` sonekini **ondalık** (10^6 byte) okur,
bizim hesaplar binary (2^20). `mkpart 3944MB` → sektör 7703125 (hizasız, boş alana
uymaz) → "Becerebildiğimiz buna en yakın konum..." hatası. ÇÖZÜM: parted'a daima
`"{}MiB"` sonekiyle geç (`jobs/partition.rs`, hem otomatik hem CustomPartitionJob).
Tamsayı MiB = 2048 sektörün katı → hizalama hatası çıkmaz. UI'da kullanıcıya MB
gösterilir ama parted argümanı asla "MB" olmasın.
- MB dönüşümleri tek bölme: `x / 1_048_576` (çift `x/1024/1024` hassasiyet kaybı yapardı).
- Disk seçiminde `size_gb * 1024` yerine `size_bytes / 1_048_576`.
- Mountpoint değişince boyut alanı SADECE boş/"0"/"remaining" ise varsayılanlanır
(kullanıcının elle girdiği değer ezilmez).
- Simge fontu: `assets/remixicon.ttf` egui fallback fontu (`ui/theme.rs setup_fonts`).
Kutucuk görürsen karakter fontta yok demektir — fontTools ile cmap kontrol et:
kullanılanlar: EB7B check, EA6B arrow, EC28 trash, EA13 add, EFE0 pencil, EECD lock,
ECA1 error, EDFB hard-drive-line, ECB5/B7 eye, F4C6 close.
- Alt bar: "Mevcut Durum / Kurulum Sonrası" çift şerit (`manual_dual_bars`,
gri segment = boş) — SADECE seçili disk için gösterilir (`state.selected_disk`).
## Bilinen Açıklar / Yapılacaklar
- [ ] VM testinde rapor edilen hatalar düzeltilecek (kullanıcı listeleyecek)
- [ ] `manual_capacity_error` yalnızca sabit boyutları kontrol eder; LVM PV/LV toplamları ayrıca doğrulanmalı
- [ ] Silinmek işaretli bölüm bir VG'nin PV'siyse SetupLvmJob tarafında kontrol yok
- [ ] Otomatik mod (erase disk) hâlâ tek disk; çoklu otomatik plan yok
- [ ] Debug amaçlı kalan `eprintln!` satırları temizlenebilir
## Test / Derleme
- `cargo check` + `cargo test` (22 birim testi; partition modülü ağırlıklı).
- VM'de denemek: ISO'yu boot edip manuel bölümlemede sdb üzerindeki senaryolar:
sil→boş-alan birleşimi, geri al, kalan alan, farklı diske bölüm ekleme.
+15
View File
@@ -141,6 +141,13 @@ display_manager_autologin_hint = "(first user)"
display_manager_de_not_found = "(not detected)"
display_manager_note = "Note: The display manager will be enabled by default on the installed system."
mp_size_remaining = "Remaining Space"
mp_size_available = "Available"
mp_create_here = "Create Here"
mp_will_delete = "will be deleted"
mp_restore = "Restore"
mp_free_space = "Free Space"
mp_bar_now = "Current Layout"
mp_bar_after = "After Installation"
# ── UI / General ──────────────────────────────────────────
installer = "YALI Installation Tool"
@@ -235,6 +242,14 @@ mp_skip_format = "Skip Formatting (preserve existing data)"
mp_size_gb = "%{n} GB"
mp_size_mb = "%{n} MB"
# ── Table Column Headers ────────────────────────────────────
column_device = "Device"
column_mount_point = "Mount Point"
column_label = "Label"
column_type = "Type"
column_format = "Format"
column_size = "Size"
check_ram = "RAM"
check_ram_fail = "%{mb} MB RAM available. Minimum 1 GB required."
+16
View File
@@ -117,6 +117,14 @@ disk_too_small = "Disk çok küçük"
summary_partition_plan = "Bölümleme"
summary_custom_partitions = "Manuel Bölümler"
mp_size_remaining = "Kalan Alan"
mp_size_available = "Kullanılabilir"
mp_create_here = "Buraya Oluştur"
mp_will_delete = "silinecek"
mp_restore = "Geri Al"
mp_free_space = "Boş Alan"
mp_bar_now = "Mevcut Durum"
mp_bar_after = "Kurulum Sonrası"
# ── UI / Genel ────────────────────────────────────────────
installer = "PisiLinux Kurulum Aracı"
@@ -206,6 +214,14 @@ mp_skip_format = "Biçimlendirmeyi Atla (mevcut veri korunur)"
mp_size_gb = "%{n} GB"
mp_size_mb = "%{n} MB"
# ── Tablo Sütun Başlıkları ────────────────────────────────────
column_device = "Aygıt"
column_mount_point = "Bağlama Noktası"
column_label = "Etiket"
column_type = "Tür"
column_format = "Biçim"
column_size = "Boyut"
check_ram = "RAM"
check_ram_fail = "%{mb} MB RAM mevcut. Kurulum için minimum 1 GB gerekli."
+81 -1
View File
@@ -113,6 +113,7 @@ impl std::fmt::Display for PartitionTableType {
/// Manuel bölümleme için desteklenen dosya sistemleri.
#[derive(Clone, Debug, PartialEq)]
#[allow(dead_code)]
pub enum FsType {
Ext4,
Ext3,
@@ -122,7 +123,6 @@ pub enum FsType {
Ntfs,
Swap,
Lvm, // LVM fiziksel bölüm (PV)
#[allow(dead_code)]
LvmLv, // LVM mantıksal bölüm (LV)
}
@@ -150,6 +150,7 @@ impl std::fmt::Display for FsType {
/// LVM Volume Group yapılandırması.
#[derive(Clone, Debug, Default)]
#[allow(dead_code)]
pub struct VolumeGroup {
pub name: String,
pub pv_devices: Vec<String>, // PV olarak kullanılacak bölüm aygıtları
@@ -167,11 +168,86 @@ pub struct LogicalVolume {
pub mountpoint: String,
}
/// Cihaz yolundan ait olduğu diskin yolunu çıkarır.
///
/// - `/dev/sda1` → `/dev/sda`
/// - `/dev/nvme0n1p2` → `/dev/nvme0n1`
/// - `/dev/mmcblk0p1` → `/dev/mmcblk0`
#[allow(dead_code)] // genel yardımcı; testlerde ve gelecekteki işlerde kullanılır
pub fn device_to_disk(device: &str) -> String {
let base = device.trim_start_matches("/dev/");
// NVMe/MMC/loop aygıtlarında bölüm ayracı "p" + rakamdır
let needs_p = base.contains("nvme") || base.contains("mmcblk") || base.contains("loop");
if let Some(pos) = base.rfind('p') {
if needs_p && base[pos + 1..].chars().all(|c| c.is_ascii_digit()) && !base[pos + 1..].is_empty() {
return format!("/dev/{}", &base[..pos]);
}
}
// SATA/virtio: sondaki tüm rakamları at
let trimmed = base.trim_end_matches(|c: char| c.is_ascii_digit());
format!("/dev/{}", trimmed)
}
/// Cihaz adından bölüm numarasını çıkarır ("/dev/sda3" → 3,
/// "/dev/nvme0n1p2" → 2). Sonda rakam yoksa None döner.
pub fn part_number(device: &str) -> Option<u32> {
let base = device.trim_start_matches("/dev/");
let digits: String = base.chars().rev().take_while(|c| c.is_ascii_digit()).collect();
digits.chars().rev().collect::<String>().parse().ok()
}
/// Bölüm tanımlarına parted'ın gerçek slot davranışını izleyerek
/// aygıt numarası atar.
///
/// Eski Yali modeliyle aynı mantık: fiziksel (silinmemiş) bölümler
/// kendi numaralarını korur; silinmek işaretlenenlerin slotları boşalır
/// ve yeni tanımlar en küçük boş slotu sırayla doldurur. Örn. sda2
/// silinip yeni bölüm eklenirse yeni bölüm sda2 olur; sda3 yerinde kalır.
pub fn assign_partition_numbers(parts: &mut [CustomPartition]) {
use std::collections::BTreeSet;
// Disk bazlı kullanılan slot kümeleri
let mut used: std::collections::HashMap<String, BTreeSet<u32>> = std::collections::HashMap::new();
for p in parts.iter() {
if p.delete { continue; } // silinecekler slot açar
if !p.orig_device.is_empty() { // fiziksel: kendi numarası
if let Some(n) = part_number(&p.orig_device) {
used.entry(p.disk.clone()).or_default().insert(n);
}
}
}
for p in parts.iter_mut() {
if p.delete {
// Silinecek bölüm: kurulumda yok olacak; adı olduğu gibi kalır,
// yeni slot tüketmez.
continue;
}
match part_number(&p.orig_device) {
Some(n) => { p.device = part_path(&p.disk, n); }
None => {
let set = used.entry(p.disk.clone()).or_default();
// en küçük boş slot
let mut n = 1u32;
while set.contains(&n) { n += 1; }
set.insert(n);
p.device = part_path(&p.disk, n);
}
}
}
}
/// Kullanıcının manuel olarak tanımladığı tek bir bölüm.
#[derive(Clone, Debug)]
pub struct CustomPartition {
/// Bölümün ait olduğu disk (ör. "/dev/sda").
/// Çoklu disk kurulumunda bölümler diske göre gruplanır.
pub disk: String,
/// Bölüm aygit adı; oluşturulacak (ör. "/dev/sda1")
pub device: String,
/// Taramada bulunan fiziksel cihaz adı (örn. "/dev/sda3").
/// Kullanıcı tarafından yeni eklenen tanımlarda boştur.
/// Silme işleminde satırın listede kalıp yeniden
/// tanımlanabilmesini sağlar.
pub orig_device: String,
/// Boyut MB cinsinden; 0 = "kalan alan"
pub size_mb: u64,
/// Dosya sistemi
@@ -186,6 +262,10 @@ pub struct CustomPartition {
pub luks_name: String,
/// false = biçimlendir, true = olduğu gibi bırak (mevcut bölümleri koru)
pub skip_format: bool,
/// true = kurulum sırasında bu bölüm diskten SİLİNECEK
/// (eski Yali'deki OperationDestroyDevice karşılığı).
/// Satır listede görünür kalır; istenirse işaret geri alınır.
pub delete: bool,
}
/// Kurulum boyunca toplanan tüm veriler.
+1 -1
View File
@@ -633,7 +633,7 @@ pub fn build_custom_job_queue(
let src = if cfg.source.is_empty() { detect_source_dir() } else { cfg.source };
let mut jobs: Vec<Box<dyn Job>> = vec![
Box::new(CustomPartitionJob { disk: disk.clone(), custom_partitions: custom_partitions.clone() }),
Box::new(CustomPartitionJob { custom_partitions: custom_partitions.clone() }),
Box::new(SetupLvmJob { volume_groups: volume_groups.clone(), logical_volumes: logical_volumes.clone() }),
Box::new(MountCustomPartitionsJob { custom_partitions: custom_partitions.clone(), logical_volumes: logical_volumes.clone(), mount: mt.clone() }),
Box::new(MountBindJob { mount: mt.clone() }),
+210 -66
View File
@@ -204,15 +204,15 @@ impl Job for PartitionJob {
ui.log(format!("parted {} mktable {}", disk, table_label));
run_parted(&["-s", disk, "mktable", table_label]).await?;
let mut next_mb: u64 = 1; // İlk MB'ı boot kayıtları için bırak
let mut next_mb: u64 = 1; // İlk MiB'ı boot kayıtları için bırak
// 2. EFI bölümü (yalnızca GPT/UEFI)
if self.plan.efi_mb > 0 {
let end_mb = next_mb + self.plan.efi_mb;
ui.log(format!("EFI bölümü oluşturuluyor: {}{} MB", next_mb, end_mb));
ui.log(format!("EFI bölümü oluşturuluyor: {}{} MiB", next_mb, end_mb));
run_parted(&[
"-s", disk, "mkpart", "fat32",
&format!("{}MB", next_mb), &format!("{}MB", end_mb),
&format!("{}MiB", next_mb), &format!("{}MiB", end_mb),
]).await?;
run_parted(&["-s", disk, "set", "1", "esp", "on"]).await?;
next_mb = end_mb;
@@ -221,16 +221,16 @@ impl Job for PartitionJob {
// 3. Swap bölümü
let swap_end = next_mb + self.plan.swap_mb;
let swap_num: u32 = if self.plan.efi_mb > 0 { 2 } else { 1 };
ui.log(format!("Swap bölümü oluşturuluyor: {}{} MB", next_mb, swap_end));
ui.log(format!("Swap bölümü oluşturuluyor: {}{} MiB", next_mb, swap_end));
run_parted(&[
"-s", disk, "mkpart", "linux-swap",
&format!("{}MB", next_mb), &format!("{}MB", swap_end),
&format!("{}MiB", next_mb), &format!("{}MiB", swap_end),
]).await?;
next_mb = swap_end;
// 4. Kök bölümü (kalan alan)
let root_num: u32 = if self.plan.efi_mb > 0 { 3 } else { 2 };
ui.log(format!("Kök bölümü oluşturuluyor: {}MB disk sonu", next_mb));
ui.log(format!("Kök bölümü oluşturuluyor: {}MiB disk sonu", next_mb));
run_parted(&[
"-s", disk, "mkpart", "ext4",
&format!("{}MB", next_mb), "100%",
@@ -448,9 +448,75 @@ async fn read_partition_uuids(
// MANUEL BÖLÜMLEME İÇİN DESTEKLEYİCİ JOB'LAR
// ─────────────────────────────────────────────
/// Manuel bölümleme ayarlarını disk üzerinde uygulayan ve biçimlendiren Job.
/// Diskteki boş bölgeleri MB cinsinden hesaplar (sysfs tabanlı).
///
/// `exclude_nums`: silinecek işaretli bölümler — boşluk olarak
/// davranılır (kurulum sırasında önce silindikleri için).
/// Dönen değer: (başlangıç_mb, bitiş_mb) aralıkları; disk sonunda
/// GPT yedek başlığı için 1MB pay bırakılır.
fn disk_free_regions_mb(disk: &str, exclude_nums: &[u32]) -> Result<Vec<(u64, u64)>, String> {
let base = disk.trim_start_matches("/dev/");
const SECTOR: u64 = 512;
const MB: u64 = 1024 * 1024;
let disk_sectors: u64 = std::fs::read_to_string(format!("/sys/block/{}/size", base))
.map_err(|e| format!("{} boyutu okunamadı: {}", disk, e))?
.trim().parse().unwrap_or(0);
if disk_sectors == 0 {
return Err(format!("{} disk boyutu okunamadı", disk));
}
let total_mb = disk_sectors * SECTOR / MB;
let usable_total = total_mb.saturating_sub(1); // disk sonu güvenlik payı
// Dolu aralıklar
let mut occupied: Vec<(u64, u64)> = Vec::new();
if let Ok(entries) = std::fs::read_dir(format!("/sys/block/{}", base)) {
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().to_string();
if !name.starts_with(base) || name == base { continue; }
let num = match crate::installer::part_number(&format!("/dev/{}", name)) {
Some(n) => n,
None => continue,
};
if exclude_nums.contains(&num) { continue; }
let start_sectors: u64 = std::fs::read_to_string(entry.path().join("start"))
.ok().and_then(|s| s.trim().parse().ok()).unwrap_or(0);
let len_sectors: u64 = std::fs::read_to_string(entry.path().join("size"))
.ok().and_then(|s| s.trim().parse().ok()).unwrap_or(0);
if len_sectors == 0 { continue; }
let start_mb = (start_sectors * SECTOR + MB - 1) / MB;
let end_mb = ((start_sectors + len_sectors) * SECTOR) / MB;
if end_mb > start_mb {
occupied.push((start_mb, end_mb.min(usable_total)));
}
}
}
occupied.sort();
// Boşlukları çıkar (ilk 1MB boot alanı hariç)
const HEADROOM_MB: u64 = 1;
let mut regions: Vec<(u64, u64)> = Vec::new();
let mut cursor = HEADROOM_MB.min(usable_total);
for (s, e) in &occupied {
if *s > cursor { regions.push((cursor, *s)); }
cursor = cursor.max(*e);
}
if cursor < usable_total { regions.push((cursor, usable_total)); }
regions.retain(|(s, e)| e > s);
Ok(regions)
}
/// Manuel bölümleme ayarlarını granular (eski Yali Operation modeli)
/// yaklaşımla uygulayan Job.
///
/// Eski Yali'nin DeviceTree/Operation mimarisiyle aynı felsefe:
/// - Dokunulmayan disklere ve bölümlere HİÇ dokunulmaz (veriler korunur)
/// - Silinmek işaretli bölümler `parted rm` ile kaldırılır
/// (OperationDestroyDevice karşılığı)
/// - Yeni tanımlar diskin BOŞ alanlarına yerleştirilir; tüm disk
/// sıfırlanmaz (OperationCreateDevice karşılığı)
/// - skip_format=false olanlar biçimlendirilir (OperationCreateFormat)
pub struct CustomPartitionJob {
pub disk: String,
pub custom_partitions: Vec<CustomPartition>,
}
@@ -460,68 +526,145 @@ impl Job for CustomPartitionJob {
async fn run(&self, ui: &UiSender) -> Result<(), String> {
let is_uefi = std::path::Path::new("/sys/firmware/efi").exists();
// 0. Diski hazırla: bağlamaları ayır, kernel tablosunu temizle
prepare_disk(&self.disk, ui).await;
let is_gpt = is_uefi; // Genellikle UEFI ise GPT kullanılır
// MBR'de "primary" zorunludur, GPT'de ise bu alan isim (label) alanıdır.
let part_type_or_name = if is_gpt { "" } else { "primary" };
let part_type_or_name = if is_uefi { "" } else { "primary" };
// 1. Bölüm tablosu oluştur
let table_label = if is_uefi { "gpt" } else { "msdos" };
ui.log(format!("parted {} mktable {}", self.disk, table_label));
run_parted(&["-s", &self.disk, "mktable", table_label]).await?;
let mut next_mb: u64 = 1; // İlk MB boot sektörü
// 2. Bölümleri parted ile oluştur
for (i, part) in self.custom_partitions.iter().enumerate() {
let part_num = (i + 1) as u32;
let end_str = if part.size_mb == 0 {
"100%".to_string()
} else {
format!("{}MB", next_mb + part.size_mb)
};
let start_str = format!("{}MB", next_mb);
let fs_label = match part.fstype {
FsType::Fat32 => "fat32",
FsType::Swap => "linux-swap",
FsType::Ext4 => "ext4",
FsType::Ext3 => "ext3",
FsType::Btrfs => "btrfs",
FsType::Xfs => "xfs",
FsType::Ntfs => "ntfs",
// LVM PV için parted "ext4" ile işaretle, pvcreate sonra
FsType::Lvm => "ext4",
// LVM LV doğrudan parted ile oluşturulmaz
FsType::LvmLv => continue,
};
ui.log(format!(
"Bölüm {} oluşturuluyor: {} - {} ({})",
part_num, start_str, end_str, fs_label
));
run_parted(&[
"-s", &self.disk, "mkpart", part_type_or_name, fs_label,
&start_str, &end_str
]).await?;
if part.mountpoint == "/boot/efi" || part.fstype == FsType::Fat32 {
run_parted(&["-s", &self.disk, "set", &part_num.to_string(), "esp", "on"]).await?;
}
if part.size_mb > 0 {
next_mb += part.size_mb;
// 0. İşlem yapılan diskleri tanım sırasına göre topla
let mut disks: Vec<String> = Vec::new();
for part in &self.custom_partitions {
if !disks.contains(&part.disk) {
disks.push(part.disk.clone());
}
}
// 2.5. Tüm mkpart bitti; kernel'e bildir ve aygıt düğümlerini bekle
sync_partition_table(&self.disk).await;
for disk in &disks {
let parts: Vec<&CustomPartition> = self.custom_partitions.iter()
.filter(|p| p.disk == *disk).collect();
// 3. Biçimlendir (LUKS ile şifreleme desteklenir)
// Bu disk için planlanan işlemler
let destroys: Vec<u32> = parts.iter()
.filter(|p| p.delete && !p.orig_device.is_empty())
.filter_map(|p| crate::installer::part_number(&p.orig_device))
.collect();
let mut creates: Vec<&CustomPartition> = parts.iter()
.filter(|p| !p.delete && p.orig_device.is_empty() && p.fstype != FsType::LvmLv)
.map(|p| *p)
.collect();
// Sabit boyutlular ÖNCE yerleşir; "kalan alan" (size 0)
// tanımları EN SON, geriye kalan boşluğa yerleştirilir.
// (Stable sort → aynı grupta tanım sırası korunur.)
creates.sort_by_key(|p| p.size_mb == 0);
let has_format = parts.iter().any(|p| !p.delete && !p.skip_format);
if destroys.is_empty() && creates.is_empty() && !has_format {
ui.log(format!("⏭️ {}: işlem yok, korunuyor", disk));
continue; // eski Yali gibi dokunulmayan diske girilmez
}
ui.log(format!("── Disk işleniyor: {} ──", disk));
// Bağlamaları ayır, swap kapat
prepare_disk(disk, ui).await;
// ── FAZ A: SİLMEK İŞARETLİ BÖLÜMLER (büyükten küçüğe numara) ──
let mut del_sorted = destroys.clone();
del_sorted.sort_unstable_by(|a, b| b.cmp(a));
for n in &del_sorted {
ui.log(format!("parted {} rm {} (bölüm siliniyor)", disk, n));
run_parted(&["-s", disk, "rm", &n.to_string()]).await?;
}
if !del_sorted.is_empty() {
sync_partition_table(disk).await;
}
// ── FAZ B: YENİ BÖLÜMLER (boş alana yerleştir) ──────────────
if !creates.is_empty() {
let mut regions = disk_free_regions_mb(disk, &destroys)?;
for part in &creates {
let fs_label = match part.fstype {
FsType::Fat32 => "fat32",
FsType::Swap => "linux-swap",
FsType::Ext4 => "ext4",
FsType::Ext3 => "ext3",
FsType::Btrfs => "btrfs",
FsType::Xfs => "xfs",
FsType::Ntfs => "ntfs",
// LVM PV için parted "ext4" ile işaretle, pvcreate sonra
FsType::Lvm => "ext4",
FsType::LvmLv => continue,
};
// Yerleşim: boyut belirtilmişse sığan İLK boş bölge,
// "kalan alan" ise en büyük bölgenin tamamı.
let need = part.size_mb;
let placement = if need == 0 {
let idx = regions.iter().enumerate()
.max_by_key(|(_, (s, e))| e.saturating_sub(*s))
.map(|(i, _)| i);
match idx {
Some(i) => {
let r = regions.remove(i);
Some(r)
}
None => None,
}
} else {
let mut found = None;
for (i, (s, e)) in regions.iter().enumerate() {
if e.saturating_sub(*s) >= need {
found = Some((i, (*s, s + need)));
break;
}
}
match found {
Some((i, seg)) => {
let (_, e) = regions[i];
let start = seg.0;
let new_start = start + need;
if e > new_start {
regions[i] = (new_start, e);
} else {
regions.remove(i);
}
Some(seg)
}
None => None,
}
};
let (start_mb, end_mb) = placement.ok_or_else(|| format!(
"{} üzerinde {} MB için yeterli boş alan yok", disk, need))?;
// DİKKAT: parted "MB" sonekini onalık (10^6) okur;
// hesaplarımız binary (2^20) olduğu için "MiB" zorunlu.
// Tamsayı MiB = 2048 sektörün katı → parted hizalama
// hatası vermez.
let start_str = format!("{}MiB", start_mb);
let end_str = format!("{}MiB", end_mb);
let slot = crate::installer::part_number(&part.device).unwrap_or(0);
ui.log(format!(
"{} üzerinde yeni bölüm oluşturuluyor: {} - {} ({})",
disk, start_str, end_str, fs_label
));
run_parted(&[
"-s", disk, "mkpart", part_type_or_name, fs_label,
&start_str, &end_str
]).await?;
if part.mountpoint == "/boot/efi" || part.fstype == FsType::Fat32 {
run_parted(&["-s", disk, "set", &slot.to_string(), "esp", "on"]).await?;
}
}
sync_partition_table(disk).await;
}
// ── FAZ C: BİÇİMLENDİRME aşağıdaki ortak döngüde ────────────
}
// Biçimlendir (LUKS ile şifreleme desteklenir) — tüm diskler.
// Silinmek işaretli bölümlere hiç dokunulmaz.
for part in &self.custom_partitions {
if part.delete { continue; }
let path = &part.device;
// LUKS şifreleme (kök veya isteğe bağlı bölümler)
@@ -667,7 +810,8 @@ impl Job for MountCustomPartitionsJob {
async fn run(&self, ui: &UiSender) -> Result<(), String> {
// 1. Kök (/) bölümünü (fiziksel partition veya LVM LV) bul ve bağla
let root_device = if let Some(root_part) = self.custom_partitions.iter().find(|p| p.mountpoint == "/") {
let root_device = if let Some(root_part) = self.custom_partitions.iter()
.find(|p| p.mountpoint == "/" && !p.delete) {
self.device_path(root_part)
} else if let Some(root_lv) = self.logical_volumes.iter().find(|lv| lv.mountpoint == "/") {
format!("/dev/{}/{}", root_lv.vg_name, root_lv.name)
@@ -687,7 +831,7 @@ impl Job for MountCustomPartitionsJob {
let mut mount_items = Vec::new();
for part in &self.custom_partitions {
if part.mountpoint != "/" && part.fstype != FsType::Swap && !part.mountpoint.is_empty() {
if part.mountpoint != "/" && part.fstype != FsType::Swap && !part.mountpoint.is_empty() && !part.delete {
mount_items.push(MountItem {
device: self.device_path(part),
mountpoint: part.mountpoint.clone(),
@@ -747,7 +891,7 @@ impl Job for FstabFromCustomPartitionsJob {
);
for part in &self.custom_partitions {
if part.mountpoint.is_empty() { continue; }
if part.mountpoint.is_empty() || part.delete { continue; }
let is_encrypted = part.encrypt && !part.luks_password.is_empty();
+235 -1274
View File
File diff suppressed because it is too large Load Diff
+55 -15
View File
@@ -43,15 +43,25 @@ impl InstallerStep for SummaryStep {
};
summary_row(ui, t!("summary_keyboard"), &kb);
let disk_str = match &state.selected_disk {
Some(d) => {
if state.erase_disk {
format!("{} \u{EA6B} {}", d, t!("summary_erase_mode"))
} else {
format!("{} \u{EA6B} {}", d, t!("summary_manual_mode"))
let disk_str = if state.erase_disk {
match &state.selected_disk {
Some(d) => format!("{} \u{EA6B} {}", d, t!("summary_erase_mode")),
None => t!("summary_not_selected").to_string(),
}
} else if !state.custom_partitions.is_empty() {
// Manuel modda tanım yapılan TÜM diskleri listele
let mut disks: Vec<&str> = Vec::new();
for part in &state.custom_partitions {
if !disks.contains(&part.disk.as_str()) {
disks.push(&part.disk);
}
}
None => t!("summary_not_selected").to_string(),
format!("{} \u{EA6B} {}", disks.join(", "), t!("summary_manual_mode"))
} else {
match &state.selected_disk {
Some(d) => format!("{} \u{EA6B} {}", d, t!("summary_manual_mode")),
None => t!("summary_not_selected").to_string(),
}
};
summary_row(ui, t!("summary_disk"), &disk_str);
@@ -73,15 +83,45 @@ impl InstallerStep for SummaryStep {
}
} else if !state.custom_partitions.is_empty() {
let mut plan_lines = Vec::new();
// Bölümleri ait oldukları diske göre gruplu listele
let mut seen_disks: Vec<&str> = Vec::new();
for part in &state.custom_partitions {
let size_str = if part.size_mb == 0 {
t!("mp_size_remaining").to_string()
} else if part.size_mb >= 1024 {
format!("{:.1} GB", part.size_mb as f64 / 1024.0)
} else {
format!("{} MB", part.size_mb)
};
plan_lines.push(format!("{} \u{EA6B} {} ({}, {})", part.device, part.mountpoint, part.fstype, size_str));
if !seen_disks.contains(&part.disk.as_str()) {
seen_disks.push(&part.disk);
}
}
for d in seen_disks {
plan_lines.push(format!("\u{EDFB} {}", d));
for part in state.custom_partitions.iter().filter(|p| p.disk == d) {
let size_str = if part.size_mb == 0 {
// Sembolik boyut: kalan kapasiteyi göster
let others: u64 = state.custom_partitions.iter()
.filter(|q| q.disk == part.disk && !q.delete && q.size_mb > 0)
.map(|q| q.size_mb)
.sum();
let cap = state.available_disks.iter()
.find(|dk| dk.name == part.disk)
.map(|dk| dk.size_bytes / 1_048_576)
.unwrap_or(0);
let rem = cap.saturating_sub(others);
if rem >= 1024 {
format!("{:.1} GB", rem as f64 / 1024.0)
} else {
format!("{} MB", rem)
}
} else if part.size_mb >= 1024 {
format!("{:.1} GB", part.size_mb as f64 / 1024.0)
} else {
format!("{} MB", part.size_mb)
};
if part.delete {
plan_lines.push(format!("{} \u{EA6B} {} ({}, {}) \u{ECA1} {}",
part.device, "", "", size_str, t!("mp_will_delete")));
} else {
plan_lines.push(format!("{} \u{EA6B} {} ({}, {})",
part.device, part.mountpoint, part.fstype, size_str));
}
}
}
summary_row(ui, t!("summary_custom_partitions"), &plan_lines.join("\n"));
}
+133 -16
View File
@@ -52,6 +52,8 @@ pub fn c_sidebar_text() -> Color32 { color_from_hex(SIDEBAR_TEXT.load(Ordering::
pub fn c_footer_bg() -> Color32 { color_from_hex(BG_FOOTER.load(Ordering::Relaxed)) }
pub fn c_root_bg() -> Color32 { color_from_hex(BG_ROOT.load(Ordering::Relaxed)) }
pub fn c_keyboard_bg() -> Color32 { color_from_hex(KEYBOARD_BG.load(Ordering::Relaxed)) }
//pub fn select_disk_label() -> Color32 { color_from_hex(0xFFFFFF) }
pub fn set_theme_mode(is_dark: bool) {
if is_dark {
@@ -108,10 +110,85 @@ pub fn apply(ctx: &egui::Context, is_dark: bool) {
ctx.set_visuals(build_visuals(is_dark));
}
// fn setup_fonts(ctx: &egui::Context) {
// let mut fonts = egui::FontDefinitions::default();
// // 1. ADIM: Sadece Metin (Text) Fontunu Bul ve Yükle
// let text_font_paths = [
// "/usr/share/fonts/TTF/OpenSans-Regular.ttf",
// "/usr/share/fonts/noto/NotoSans-Regular.ttf",
// "/usr/share/fonts/exo-1/Exo-Regular.otf",
// "/usr/share/fonts/exo-1/Exo2-Medium.ttf",
// "/usr/share/fonts/exo-1/Exo-Medium.otf",
// ];
// for path in text_font_paths {
// if let Ok(data) = std::fs::read(path) {
// fonts.font_data.insert(
// "system_text_font".to_owned(),
// egui::FontData::from_owned(data),
// );
// // Metin fontunu en başa (0. indeks) koyuyoruz ki varsayılan olarak bu kullanılsın
// fonts
// .families
// .entry(egui::FontFamily::Proportional)
// .or_default()
// .insert(0, "system_text_font".to_owned());
// break;
// }
// }
// // 2. ADIM: Renkli Emoji Fontunu Ara ve "Fallback" (Yedek) Olarak Sona Ekle
// // ISO ortamlarında Noto Color Emoji genellikle bu yollardan birinde olur
// let emoji_font_paths = [
// "/usr/share/fonts/noto/NotoColorEmoji.ttf",
// ];
// for path in emoji_font_paths {
// if let Ok(data) = std::fs::read(path) {
// fonts.font_data.insert(
// "system_emoji_font".to_owned(),
// egui::FontData::from_owned(data),
// );
// // ÖNEMLİ: Emojiyi listenin SONUNA (push ile) ekliyoruz.
// // Böylece egui normal harfleri sistem fontuyla yazacak,
// // harf sistem fontunda yoksa (yani emojiyse) gidip bu fonttan alacak.
// fonts
// .families
// .entry(egui::FontFamily::Proportional)
// .or_default()
// .push("system_emoji_font".to_owned());
// fonts
// .families
// .entry(egui::FontFamily::Monospace)
// .or_default()
// .insert(0, "system_emoji_font".to_owned());
// break;
// }
// }
// // 3. ADIM: Remixicon ikon fontunu projeye göm (PUA bölgesindeki karakterler için fallback)
// let remixicon_data = include_bytes!("../../assets/remixicon.ttf");
// fonts.font_data.insert(
// "remixicon".to_owned(),
// egui::FontData::from_owned(remixicon_data.to_vec()),
// );
// fonts
// .families
// .entry(egui::FontFamily::Proportional)
// .or_default()
// .push("remixicon".to_owned());
// ctx.set_fonts(fonts);
// }
fn setup_fonts(ctx: &egui::Context) {
let mut fonts = egui::FontDefinitions::default();
// 1. ADIM: Sadece Metin (Text) Fontunu Bul ve Yükle
// 1. ADIM: Metin ve Monospace için Sistem Fontu Yükleme
let text_font_paths = [
"/usr/share/fonts/TTF/OpenSans-Regular.ttf",
"/usr/share/fonts/noto/NotoSans-Regular.ttf",
@@ -120,25 +197,59 @@ fn setup_fonts(ctx: &egui::Context) {
"/usr/share/fonts/exo-1/Exo-Medium.otf",
];
let mut _text_font_loaded = false;
for path in text_font_paths {
if let Ok(data) = std::fs::read(path) {
fonts.font_data.insert(
"system_text_font".to_owned(),
egui::FontData::from_owned(data),
egui::FontData::from_owned(data).into(), // Sürüme göre .into() veya std::sync::Arc::new(...)
);
// Metin fontunu en başa (0. indeks) koyuyoruz ki varsayılan olarak bu kullanılsın
// Proportional (Orantılı) fontun başına ekle
fonts
.families
.entry(egui::FontFamily::Proportional)
.or_default()
.insert(0, "system_text_font".to_owned());
// Monospace için de bu fontu varsayılan olarak ekle (özel bir monospace bulunamadıysa)
fonts
.families
.entry(egui::FontFamily::Monospace)
.or_default()
.insert(0, "system_text_font".to_owned());
_text_font_loaded = true;
break;
}
}
// 2. ADIM: Renkli Emoji Fontunu Ara ve "Fallback" (Yedek) Olarak Sona Ekle
// ISO ortamlarında Noto Color Emoji genellikle bu yollardan birinde olur
// Ek Güvence: Özel bir Monospace fontu varsa öncelikli olarak onu ara
let mono_font_paths = [
"/usr/share/fonts/TTF/Hack-Regular.ttf",
"/usr/share/fonts/TTF/DejaVuSansMono.ttf",
"/usr/share/fonts/noto/NotoSansMono-Regular.ttf",
];
for path in mono_font_paths {
if let Ok(data) = std::fs::read(path) {
fonts.font_data.insert(
"system_mono_font".to_owned(),
egui::FontData::from_owned(data).into(),
);
// Monospace ailesinin 0. indeksine yerleştir
fonts
.families
.entry(egui::FontFamily::Monospace)
.or_default()
.insert(0, "system_mono_font".to_owned());
break;
}
}
// 2. ADIM: Color Emoji Fontunu Yedek (Fallback) Olarak EKLE
let emoji_font_paths = [
"/usr/share/fonts/noto/NotoColorEmoji.ttf",
];
@@ -147,39 +258,44 @@ fn setup_fonts(ctx: &egui::Context) {
if let Ok(data) = std::fs::read(path) {
fonts.font_data.insert(
"system_emoji_font".to_owned(),
egui::FontData::from_owned(data),
egui::FontData::from_owned(data).into(),
);
// ÖNEMLİ: Emojiyi listenin SONUNA (push ile) ekliyoruz.
// Böylece egui normal harfleri sistem fontuyla yazacak,
// harf sistem fontunda yoksa (yani emojiyse) gidip bu fonttan alacak.
// Her iki aileye de SONA (push) ekle, böylece metinler bozulmaz
fonts
.families
.entry(egui::FontFamily::Proportional)
.or_default()
.push("system_emoji_font".to_owned());
fonts
.families
.entry(egui::FontFamily::Monospace)
.or_default()
.insert(0, "system_emoji_font".to_owned());
.push("system_emoji_font".to_owned()); // insert(0, ...) yerine push yapıldı!
break;
}
}
// 3. ADIM: Remixicon ikon fontunu projeye göm (PUA bölgesindeki karakterler için fallback)
// 3. ADIM: Remixicon İkon Fontunu Göm (PUA)
let remixicon_data = include_bytes!("../../assets/remixicon.ttf");
fonts.font_data.insert(
"remixicon".to_owned(),
egui::FontData::from_owned(remixicon_data.to_vec()),
egui::FontData::from_owned(remixicon_data.to_vec()).into(),
);
fonts
.families
.entry(egui::FontFamily::Proportional)
.or_default()
.push("remixicon".to_owned());
fonts
.families
.entry(egui::FontFamily::Monospace)
.or_default()
.push("remixicon".to_owned());
ctx.set_fonts(fonts);
}
@@ -187,7 +303,7 @@ fn setup_fonts(ctx: &egui::Context) {
fn build_visuals(is_dark: bool) -> Visuals {
let mut v = if is_dark { Visuals::dark() } else { Visuals::light() };
// Genel arka planlar
v.window_fill = c_bg_dark();
v.panel_fill = c_bg_panel();
@@ -243,13 +359,14 @@ fn build_visuals(is_dark: bool) -> Visuals {
fn build_style() -> egui::Style {
let mut style = egui::Style::default();
// Genel boşluk ve boyutlar
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 = 14.0;
// Metin boyutları
style.text_styles = [
(egui::TextStyle::Small, FontId::proportional(11.0)),