## [0.4.0] - 2026-07-24

Uçtan uca bir kod denetiminin ardından gelen kararlılık sürümü: güvenlik,
işlem güvenilirliği, felaket kurtarma, tanılama ve erişilebilirlik. Yeni
özellik yerine "kapalı sanılan ama açık olan" yolları kapatmaya odaklanır.

### Güvenlik

- **Panel için isteğe bağlı TLS.** Panel yalnız düz HTTP sunuyordu; parola,
  oturum çerezi, root terminal trafiği ve veritabanı parolaları ağda açık
  gidiyordu. `WEBPANEL_TLS_CERT` ve `WEBPANEL_TLS_KEY` verilince panel HTTPS
  sunuyor, oturum çerezi `Secure` işaretleniyor ve `Strict-Transport-Security`
  başlığı ekleniyor. TLS'siz kurulumda açılışta ve arayüzde uyarı görünüyor
- **Oturum ve bilet token'ları hash'li saklanıyor.** `sessions.token` ve
  `login_challenges.token` ham duruyordu; panel DB'sine erişen biri tüm
  oturumları ele geçirebilirdi. Artık yalnız SHA-256 özeti saklanıyor
- **Güvenlik duvarı artık tek atomik işlemde yazılıyor.** Kurallar önce
  zincirler boşaltılıp tek tek ekleniyordu; ortadaki bir hata varsayılan ret
  satırını hiç yazmadan işlemi bitiriyor ve güvenlik duvarını **açık** konumda
  bırakıyordu. Kural kümesi artık `iptables-restore` ile bütün olarak uygulanır:
  ya tamamı yürürlüğe girer ya da zincirler hiç değişmez
- **IPv6 host güvenlik duvarı.** Güvenlik duvarı tümüyle IPv4'tü; varsayılan ret
  ve açık port kuralları yalnız iptables'a yazılıyordu. Çift yığınlı bir
  sunucuda kapalı sanılan portlar (SSH, panel, veritabanı) IPv6 üzerinden
  erişilebilir kalıyordu. Artık host kuralları ve varsayılan ret ip6tables'a da
  uygulanıyor; kullanıcı IPv6 kaynaklı kural da girebiliyor. ip6tables yoksa
  IPv6 kısmı güvenle atlanıyor ve arayüz uyarı gösteriyor. Docker IPv6'da
  yönetilmiyor (DOCKER-USER IPv6 tablosunda yok)
- **IPv6 kaynakları ban zincirinden ayrıldı.** Bir IPv6 adresi ban listesine
  girdiğinde iptables kural kümesinin tamamını reddediyor ve ban süresi boyunca
  güvenlik duvarı hiç güncellenemiyordu. Otomatik ban motoru şimdilik IPv4-only
  kalıyor; eşiği aşan IPv6 kaynakları uyarı merkezinde ve denemeler tablosunda
  işaretleniyor (elle IPv6 firewall kuralı girmek mümkün)

- **Güvenlik başlıkları.** Panel yalnız `Content-Type` yazıyordu; artık tüm
  yanıtlar CSP, `X-Frame-Options`, `nosniff`, `Referrer-Policy`,
  `Permissions-Policy` ve COOP taşıyor. Panel hiçbir sayfaya gömülemez —
  clickjacking ile tek tıkla yıkıcı işlem tetiklemek mümkün değil
- **Arşiv açma sıkılaştırıldı.** Yedek arşivindeki dosya izinleri olduğu gibi
  uygulanıyordu; panel root çalıştığı için `4755` modlu bir girdi diske
  setuid-root ikili olarak düşebiliyordu. Mod artık maskeleniyor. Açılan veri
  için de bütçe var: boyutunu küçük gösteren bir arşiv diski dolduramıyor
- **Giriş seli paneli durduramıyor.** Parola hash'leme async yürütücüde
  koşuyordu; birkaç eşzamanlı deneme canlı metrikleri, terminali ve tüm API'yi
  dondurabiliyordu. İşlem ayrı havuza taşındı ve eşzamanlılığı sınırlandı
- **Sırlar kayıtlardan maskeleniyor.** Uygulama kurulum komutlarının çıktısı
  ortam değişkenlerini yazdırabiliyor (`npm config`, `env`, pek çok build
  script'i) ve bu çıktı panel veritabanına ham kaydedilip arayüzde
  gösteriliyordu — yani `.env`'deki veritabanı parolaları oraya düşüyordu.
  Veritabanı ve SFTP parolaları, git token'ları ve sır görünümlü uygulama
  ortam değişkenleri artık kurulum çıktısında, cron çıktısında ve denetim
  günlüğünde `***` olarak görünüyor
- **Denetim günlüğü artık adresi de yazıyor.** "Bu işlemi kim yaptı" sorusunun
  ağ tarafı cevapsızdı; yalnız giriş kayıtlarında IP vardı. Site silme,
  veritabanı silme, güvenlik duvarı değişikliği gibi tüm kayıtlar artık isteğin
  geldiği adresi taşıyor. Vekil arkasında adres yalnız panel için gerçek IP
  güveni açıkken ve bağlantı güvenilen ağdan geliyorsa başlıktan okunuyor —
  aksi halde bir başlıkla uydurulabilirdi
- **SFTP hesabı artık sessizce çalışmaz durumda kalmıyor.** Sunucuda `openssl`
  yoksa parola `users.conf` dosyasına açık yazılıyordu ama satırdaki
  "şifrelenmiş" bayrağı yüzünden sshd girişleri reddediyordu: panel hesabı
  oluşturup parolayı gösteriyor, bağlantı hiç çalışmıyor ve sebebi hiçbir yerde
  yazmıyordu. Üstelik parola diskte açık kalıyordu. Artık hash üretilemezse
  hesap oluşturulmuyor ve ne yapılacağı söyleniyor
- **Uygulama ve kurulum container'larına bellek sınırı.** Kaçak bir uygulama ya
  da `npm ci` benzeri bir kurulum komutu host belleğini tüketip paneli ve tüm
  siteleri düşürebiliyordu. Her ikisi de artık sunucu belleğinin dörtte biriyle
  (en az 256 MiB) sınırlı

### Güvenilirlik

- **Panel yedeği ve felaket kurtarma.** Panelin kendi verisi (veritabanı, ACME
  hesapları ve sertifikaları, App Store kurulumları, SFTP hesapları, harici
  manifestler) hiçbir yedekleme akışında yoktu; disk ölürse siteler kurtarılsa
  bile hangi sitenin hangi türde olduğu, sertifikalar, güvenlik duvarı kuralları
  ve kimlikler kaybolurdu. Yeni **Panel yedeği** cron türü bunların tümünü tek
  bir arşive alıyor (veritabanı `VACUUM INTO` ile tutarlı). Temiz bir sunucuda
  `webpanel restore --panel <arşiv>` ile geri yükleniyor; işlem atomik ve daha
  yeni bir sürümden gelen yedeği reddediyor
- **Migration geri dönüşü artık paneli kilitlemiyor.** Güncelleme başarısız
  olup eski sürüme dönüldüğünde, veritabanında eski binary'nin tanımadığı yeni
  migration'lar bulunuyor ve panel hiç açılmıyordu (`VersionMissing`).
  Migration'lar ekleme-only olduğu için artık `ignore_missing` ile eski binary
  yeni şemayla açılıyor. Yarıda kalan bir migration (`Dirty`) da ham hata yerine
  ne yapılacağını söyleyen bir mesaj veriyor
- **Güncelleme öncesi otomatik veritabanı anlık görüntüsü.** `webpanel-update`
  binary'yi değiştirmeden önce `VACUUM INTO` ile tutarlı bir kopya alıyor
  (`/opt/webpanel/releases/pre-update.db`); migration'ın şemayı bozduğu ender
  durumlar için son çare geri dönüş noktası. `webpanel backup-db <hedef>` komutu
  bunu elle de sağlıyor
- **Erişilemeyen bir git deposu paneli kilitlemiyor.** Dağıtımdaki git çağrısının
  zaman aşımı yoktu; ulaşılamayan bir depo site işlemleri kilidini dakikalarca
  tutuyor ve bu sürede site oluşturma, sertifika yenileme, IP kuralı ve hız
  sınırı işlemlerinin **tümü** bekliyordu. Git çağrıları artık en fazla beş
  dakika sürüyor ve yavaş aktarımlar kendiliğinden kesiliyor
- **Başarısız dağıtım siteyi bozuk bırakmıyor.** Kaynak güncellenir güncellenmez
  yeni kod çalışan uygulamaya bağlanıyor, kurulum komutu ondan sonra
  çalışıyordu; kurulum düşerse site "yeni kod + eski bağımlılıklar" ile kalıyor
  ve çoğu uygulamada bu anında hata demekti. Kurulum başarısız olursa kaynak
  dağıtım öncesi sürüme geri alınıp uygulama yeniden başlatılıyor
- **Yarım kalan site oluşturma kayıt bırakmıyor.** Disk dolduğunda ya da izin
  hatasında site satırı veritabanında kalıyor, dosyaları ve container'ı
  oluşmuyordu; kullanıcı hata görüyor ama site listede duruyor ve aynı alan
  adıyla ikinci deneme "kullanılıyor" diyordu. Artık başarısız oluşturma hiçbir
  iz bırakmıyor
- **Eşzamanlı istekler nginx yapılandırmasını bozamıyor.** Hız sınırı ve IP
  kuralı uçları site kilidini almadan vhost'ların tamamını yeniden üretiyordu;
  bir site güncellenirken araya giren bir istek yarım yazılmış bir yapılandırmayı
  test ettirebiliyordu. Tüm nginx değişiklikleri artık tek sırada
- **Bozuk özel vhost bloğu tüm siteleri düşüremiyor.** Snippet yalnız boyut ve
  NUL denetiminden geçiyordu; dengesiz bir süslü parantez sunucu bloğunu erken
  kapatıp `nginx -t`'yi düşürüyor ve o andan sonra **hiçbir** site değişikliği
  uygulanamıyordu. Parantez dengesi artık kaydetmeden önce doğrulanıyor
- **Zamanlanmış görevler üst üste binmiyor.** Önceki çalıştırma sürerken görev
  yeniden tetikleniyordu; beş dakikada bir çalışan ve on dakika süren bir
  yedekleme diski ve CPU'yu tüketebiliyordu. Süren bir görev varsa tur atlanıyor
  ve bu geçmişte görünüyor
- **Panel yeniden başladığında durumlar gerçekle uzlaştırılıyor.** Bir işlem
  ortasında panel kapanırsa kayıtlar sonsuza dek "çalışıyor" kalıyor, arayüz
  yanlış bilgi gösteriyordu. Açılışta yarıda kalan görev çalıştırmaları ve
  dağıtımlar kapatılıyor, durduğu hâlde "çalışıyor" görünen uygulamalar
  düzeltiliyor
- **Yedek almadan önce disk kontrolü.** Disk dolduğunda yedekler yarım arşiv
  bırakıyordu; artık yeterli yer yoksa işlem başlamadan açık bir hata veriyor
- **Bozuk bir arşiv çalışan veritabanını silmiyor.** "Önce sıfırla" seçeneği
  açıkken geri yükleme, hedefi düşürüp dökümü yüklüyordu; döküm bozuksa
  veritabanı boş kalıyordu. Arşiv artık sıfırlamadan **önce** ön kontrolden
  geçiyor (boş dosya, yanlışlıkla seçilmiş zip/gzip/SQLite, bozuk içerik) ve
  yükleme yine de düşerse güvenlik yedeği kendiliğinden geri yükleniyor
- **Büyük dökümler belleği doldurmuyor.** Geri yükleme dökümün tamamını belleğe
  alıyordu; 2 GB'lık bir döküm küçük bir sunucuda paneli düşürebiliyordu. Döküm
  artık parça parça besleniyor
- **Geri yüklemeler tek sırada.** İki eşzamanlı geri yükleme aynı geçici dizini
  kullanıp birbirinin işini bozabiliyordu
- **Uygulama sitesi geri yüklendiğinde container yeniden başlatılıyor** — yoksa
  çalışan uygulama eski dosyaları görmeye devam ediyordu
- **`webpanel backup verify <arşiv>`**: bir yedeğin gerçekten açılabildiğini ve
  beklenen yapıda olduğunu felaket anından önce gösterir

### Tanılama

- **`webpanel doctor`** ve panelde **Sistem Sağlığı** sayfası. Bir sorun
  çıktığında elde yalnız `journalctl` vardı. On bir kontrol tek bir yerden
  çalışıyor: Docker motoru, disk alanı, veri dizinleri, veritabanı bütünlüğü,
  şema/migration durumu (yarıda kalan migration dahil), nginx ve yapılandırma
  doğrulaması, güvenlik duvarı, sertifika süreleri, yedek görevleri ve arşiv
  yaşı, panel TLS, SSH risk birleşimi, SFTP portu. Her bulgu ne yapılacağını da
  söylüyor; kontroller salt okunur. `--json` ile makine okunur çıktı verir,
  hata varsa sıfırdan farklı çıkış koduyla döner
- **`webpanel repair [--all|--firewall|--nginx]`**: panelin bildirimsel olarak
  zaten ürettiği çıktıları (iptables zincirleri, vhost dosyaları) yeniden yazar
  ve durum kayıtlarını uzlaştırır. Kullanıcı verisine dokunmaz
- **Yedeksizlik uyarısı.** Panel yedeği tanımlı değilse, hiç üretilmemişse ya da
  yedi günden eskiyse uyarı merkezinde görünüyor. Panel yedeği olmadan site
  arşivleri tek başına işe yaramaz

### Arayüz

- **Diyaloglarda odak yönetimi.** Bir diyalog açıldığında klavye odağı hâlâ
  arkadaki sayfadaydı ve Tab ile arka plana geçilebiliyordu; ekran okuyucu
  başlığı duyurmuyordu. Odak artık diyaloga alınıyor, içinde hapsediliyor ve
  kapanışta tetikleyen öğeye geri veriliyor. Yıkıcı onaylarda başlangıç odağı
  **Vazgeç**'te: Enter refleksi silmeye değil vazgeçmeye gider
- **Durumlar yalnız renkle anlatılmıyor.** Tablo satırlarındaki durum noktası
  tek başına renkti; renk körü kullanıcılar ve ekran okuyucular için okunamazdı.
  Artık her göstergede metin karşılığı var. Docker durumları da Türkçe
  gösteriliyor ve yeniden başlatma döngüsündeki bir container "çalışıyor"
  görünmüyor
- **Kırpılan değerlere erişim.** Uzun alan adı, vekil hedefi ve dosya yolları
  sessizce kesiliyordu; tam değer artık üzerine gelince görünüyor
- **Okunabilirlik.** İkincil metin rengi açık temada da koyu temada da WCAG AA
  kontrast eşiğinin altındaydı; ikisi de düzeltildi
- **Panel güncellenirken açık sekmeler çıkışa atılmıyor.** Servis birkaç saniye
  kapalı kaldığında her hata "oturum düştü" sayılıyor, kullanıcı giriş ekranına
  gidiyor ve doldurduğu form gidiyordu. Geçici hatalar artık yeniden deneniyor;
  bu sırada "Panele ulaşılamıyor" bilgisi gösteriliyor

### Geliştirme

- Sürekli entegrasyon: her push ve pull request'te biçim denetimi, clippy,
  workspace testleri ve frontend tip denetimi koşuyor. Yayın workflow'u aynı
  adımları paket üretiminden önce tekrarlıyor; kırmızı bir ağaçtan sürüm
  çıkmıyor
- Her API ucunun oturum zorunlu kıldığı teste bağlandı. Uç listesi kaynak
  koddan çıkarıldığı için yeni bir rota kendiliğinden kapsanıyor; korumasız
  bırakılan bir uç derlemede değil testte yakalanıyor
- Güvenlik başlıklarının yalnız üretildiği değil gerçekten **servis edildiği**
  de test ediliyor
- Kurulum betikleri (`install.sh`, `webpanel-update`, `webpanel-uninstall`)
  artık CI'da sözdizimi denetimi ve `shellcheck`'ten geçiyor

