# Güncelleme Akışı

---

## 1. Genel Bakış

WHost **iki bağımsız güncelleme kanalı** taşır:

1. **WHost yazılım güncellemeleri** (satıcı güncelleme servisi → imzalı tarball → atomik yerinde uygulama). `/system/update/*` API uçlarının arkasındaki ajan güncelleme servisi ve ajanın günlük güncelleme zamanlayıcısı yürütür.
2. **OS paket güncellemeleri** (Debian ailesinde apt-get, RHEL ailesinde dnf). Operatörün tetiklediği yükseltmeler için aynı router'ın `/system/packages/*` bloğu, bekleyen listeyi günlük tazeleyip güvenlik sayılarını yüzeye çıkaran zamanlayıcı.

İki kanal da **root yetkisiyle** çalışır, sistem yollarına yazar ve kalıcı bir geçmiş kaydı tutar. İkisi de yönetici kimliğinin arkasındadır (oturum çerezi ya da imzalı anahtar). WHost kanalı agent kodunu değiştirmeden önce yedekler ve hata durumunda geri alır; OS kanalı işi paket yöneticisine bırakır.

---

## 2. WHost Yazılım Güncelleme Pipeline'ı

```mermaid
sequenceDiagram
    participant Operator
    participant API as POST /system/update/install
    participant US as update_service.run_install
    participant WL as WLicense (check-update / download)
    participant FS as dosya sistemi
    participant Systemd

    Operator->>API: kurulum isteği (GET /system/update/check sonrası)
    API->>API: ret: süren kurulum (UPDATE_IN_PROGRESS), süren yedek işi (BACKUP_IN_PROGRESS), henüz kontrol yok (UPDATE_CHECK_REQUIRED), daha yeni sürüm yok (UPDATE_ALREADY_LATEST), bozuk sürüm adı (UPDATE_INVALID_VERSION), daha eski sürüm (UPDATE_DOWNGRADE_REJECTED)
    API->>US: asyncio görevi; audit satırı update_install_started
    US->>US: indirme URL'sinin host'u lisans sunucularına karşı doğrulanır
    US->>FS: backup (%5) — backup_before_update açıksa yalnız agent kodunun tar'ı (/opt/whost/agent/whost_agent, yapılandırma yok) /var/whost/backups/update_<zaman>/ altına (zamanlayıcı bu klasörlerin en yeni üçünü tutar, §5); başarısız yedek loglanır ve koşum sürer
    US->>WL: check-update yeniden — yanıt veren node'dan taze indirme token'ı; teklif artık kurulumun başlatıldığı sürüm değilse koşum biter
    US->>WL: download (%15) — token'ı üreten node'dan (403 INVALID_TOKEN yanıtlayan, erişilemeyen ve sunucu hatası veren node atlanır; başka her ret koşumu vendor'ın koduyla bitirir), X-WLicense-SHA256 başlığı
    US->>US: verifying (%35) — SHA-256 = kontrol yanıtı = başlık
    US->>WL: <paket>.asc alınır — aynı node; yalnız 404 "imza yok" demektir, başka her ret koşumu bitirir
    US->>US: verifying (%40) — RSA-PSS-SHA256 ayrık imza (gömülü yayın açık anahtarı; eksik ya da geçersiz .asc kurulumu durdurur)
    US->>FS: verifying (%45) — boş alan: paketten okunan açılmış boyut bir kez açma için, bir kez daha agent ve panel ağaçlarının yanındaki hazırlık kopyaları için, artı 256 MB, ilgili her dosya sisteminde; alan yetmiyorsa koşum hiçbir şeye dokunmadan biter
    US->>FS: extracting (%50) — safe_extract (traversal yok, symlink kaçışı yok, giriş ve boyut sınırı); paketin taşıdığı agent sürümü sunulan sürüm olmalıdır, değilse koşum hiçbir şey değiştirilmeden burada biter
    US->>FS: migrating (%60) — paket taşıyorsa migrations/{from}_to_{to}.py (WHost paketleri taşımaz: bir sürümün sunucuda yaptığı değişiklikler ilk açılışında koşar)
    US->>FS: migrating (%65) — Python bağımlılıkları: paketin requirements.lock'u (her dağıtım özetleriyle sabitlenmiş; kilit taşımayan paket için requirements.txt) kurulu dağıtımlarla karşılaştırılır (ağ yok); yalnız eksik varsa ortam venv.new'a kopyalanır ve pip kopyanın içinde özet denetimiyle koşar
    US->>FS: applying (%70) — üretildiyse venv.new → rename (öncekisi venv.old olarak kalır); agent ağacı whost_agent.new → rename, öncekisi .old olarak kalır; servis birimi + daemon-reload; kurulu requirements.txt ve requirements.lock değişir (öncekisi .old olarak kalır)
    US->>FS: applying (%80) — panel.new → rename (paket ne taşırsa taşısın dizin 755, dosya 644: web sunucusu paneli yetkisiz kullanıcıyla okur), öncekisi panel.old olarak kalır; config.json yeniden yazılır
    US->>US: health_check (%85) — kurulu ağacın her düz .py modülü bellekte derlenir (bytecode dosyası yazılmaz); bütünlük manifesti değişir (.lock.old / .lock.new)
    alt dosyalar değiştirildikten sonra bir adım başarısız
        US->>FS: rolling_back — bu koşumun değiştirdiği her şey geri konur (Python ortamı, agent ağacı, servis birimi, requirements dosyası, panel ağacı, manifest), hazırlık kopyaları silinir; geri konan sürüm bu sürecin çalıştırdığı sürümdür, bu yüzden hiçbir şey yeniden başlatılmaz
        US->>US: geçmiş kaydı status=rolled_back ve hata; audit satırı update_failed
    end
    US->>FS: completed (%100) — geçmiş kaydı status=completed restart'tan ÖNCE yazılır; audit satırı update_installed
    US->>Systemd: restarting (%100) — kurulum sonrası denetim /var/lib/whost/update-recovery/ altına yazılır (sürüm kaydının kopyasıyla) ve 20 s uyuyan geçici bir servis olarak hemen başlatılır; sonra systemd-run --on-active=3s systemctl restart whost-agent; ilerleme akışı bu aşamada kapanır (açık akış eski süreci nazik kapanış boyunca ayakta tutar; birim bunu 10 s, durdurmayı 30 s ile sınırlar) ve periyodik bellek denetimi yeni süreç açılana dek ertelenir
    Note over US,Systemd: restart zamanlayıcısı bu süreç yanıt verdikten sonra ateşlenir; denetim kendi birimi olarak onu aşar
```

**Kurulum sonrası denetim.** Bir paket kod denetiminden geçtiği hâlde açılamayabilir (başka bir Python için derlenmiş modül, açılışta düşen bir import). Denetim, restart kuyruğa alınmadan önce başlatılan kendi geçici servisinde çalışır; 20 s uyur, sonra agent'ın sağlık ucunu 5 s arayla en çok 18 kez sorar (yaklaşık 90 s). İlk yanıt denetimi bitirir. (Zamanlayıcı değil servistir: agent'ın kurduğu geçici bir zamanlayıcının, agent birimi restart döngüsündeyken tetiklenme anını sürekli ertelediği ve hiç tetiklenmediği ölçüldü.) Yeni sürüm hiç yanıt vermezse denetim birimi durdurur, güncellemenin değiştirdiklerini geri koyar (`whost_agent.old`, `panel.old`, `integrity.lock.old`, `whost-agent.service.old`, güncelleme Python paketi kurduysa `venv.old`, listeyi değiştirdiyse `requirements.txt.old` — reddedilen agent ağacı, panel ağacı, manifest ve ortam `.failed` olarak tutulur), kalıcı sürüm kaydını restart'tan önce alınan kopyadan geri yükler, geçmiş satırını `rolled_back` yapar, birimi yeniden başlatır ve yaptıklarını — geri konan sürümün 10 s sonra yanıt verip vermediğiyle birlikte — agent loguna yazar (`whost.update_watch`). Rollback ile biten bir koşum restart'a hiç ulaşmaz; onun için denetim kuyruğa alınmaz.

**Kanallar ve beta programı.** `stable` isteyen sunucuya yalnız kararlı sürümler sunulur; `beta` isteyen sunucuya betalar ve kararlı sürümler. Bir beta (`X.Y.Z-beta.N`) yalnız beta kanalında yayımlanır ve hiçbir zaman zorunlu değildir; bu yüzden `security` otomatik güncelleme politikası hiçbir betayı kendiliğinden kurmaz (§5). Beta kanalı yalnız beta programına kabul edilmiş lisanslara yanıt verir; kabulü WISECP verir. Kabul edilmemiş bir lisansa kararlı kanaldan yanıt verilir ve kontrol bunu söyler: `beta_denied` true olur (`channel` alanı bu sunucunun istediği kanalı adlandırmayı sürdürür) ve sürüm kartı kararlı yanıtla birlikte program notunu ("kararlı kanal kullanıldı") gösterir. Bir betadan kurulan sunucu beta kanalıyla başlar (§8). Bir sürüm önce yalnız listelenen sunuculara açılabilir; listede olmayan sunucuya sürüm genişletilene kadar sunulmaz.

**Hangi sürüm sunulur.** Satıcının yanıtı iki sürüm adlandırır: bu sunucuya sunulan paket — güncelleme yolunun sıradaki adımı — ve o yolun son sürümü (kararlı sürümler arasında sunulan adımdan daha ileride; ulaşılabilir sürüm yoksa sunucunun kendi sürümü). Sürüm kartı, kurulum, Geçmiş satırı ve geri alma kapısı sunulan paketi kullanır; çıkarmadan sonra paketin içindeki agent ağacının `__version__` değeri bu sürüm olmalıdır (paket bütünüyle imzalıdır, yani yeni süreç bu sürümle başlar) ve başka sürüm taşıyan bir paket koşumu hiçbir şey değiştirilmeden bitirir. Kararlı sürümler arasında sunucuya her seferinde bir sürüm sunulur; bir hattın betaları arasında en yenisi.

**Bir sürümün Python bağımlılıkları.** Paket agent'ın `requirements.lock` dosyasını — agent'ın ihtiyaç duyduğu, dolaylı gelenler dahil her dağıtım, dizinin o sürüm için sunduğu her dosyanın SHA-256'sıyla tam sürüme sabitlenmiş — ve ondan çözüldüğü gevşek `requirements.txt`'yi taşır (`scripts/release/lock_requirements.py`). Hiçbir şey değiştirilmeden önce koşum kilidi (kilit taşımayan paket için gevşek listeyi) agent'ın sanal ortamında (`/opt/whost/venv`) kurulu dağıtımlarla karşılaştırır: listedeki her satırın adı ve sürümü, ayrıca o dağıtımların (extras dahil) getirdiği paketlerin varlığı. Bu denetim için hiçbir şey indirilmez; yeni bağımlılık getirmeyen bir sürüm, İnternet erişimi olmayan bir sunucuyu eskisi gibi günceller. Eksik varsa ortam `/opt/whost/venv.new`'a kopyalanır, `pip install --require-hashes -r` kopyanın yorumlayıcısıyla koşar (en çok 15 dakika; bu adım paket dizinine erişim ister ve kilitteki özetten farklı sunulan dosya reddedilir), pip'in yazdığı konsol betikleri yeniden `/opt/whost/venv`'i gösterecek şekilde düzeltilir ve uygulama adımı kopyayı yerine alır. Çalışan sürecin içe aktardığı ortama hiçbir zaman kurulum yapılmaz: pip hatası ya da dolu disk (ortamın bir kopyası + 256 MB sığmalıdır) koşumu gerekçesiyle (pip'in son satırları ya da gereken alan) `failed` bitirir ve kopya silinir; bu adım sırasında durdurulan agent geride bir `venv.new` bırakır, sonraki koşum onu siler. Başka hiçbir şeye dokunulmamıştır. Sanal ortamdan çalışmayan bir agent, sistem yorumlayıcısının paketlerini değiştirmek yerine böyle bir sürümü eksik paket listesiyle reddeder. İki listenin kurulu kopyası (`/opt/whost/agent/requirements.lock`, `requirements.txt`) sürümü izler; ortamı elle onarmak için `/opt/whost/venv/bin/python -m pip install --require-hashes -r /opt/whost/agent/requirements.lock` yeterlidir. İnternet erişimi olmayan sunucular: listedeki paketler güncellemeden önce ortama kurulur; denetim o zaman eksik bulmaz ve pip koşmaz.

**Bir koşum nereye kaydedilir.** Koşumun adımları agent loguna (`/var/log/whost/agent.log`) `whost.update_service` (güncelleme öncesi yedek, manifest, servis birimi, kuyruğa alınan denetim ve restart, reddin gerekçesi) ve `whost.router.updates` (panelden ya da API'den başlatılan koşumun nasıl bittiği; otomatik kurulumda `whost.auto_update_scheduler`) adlarıyla yazılır; kurulum sonrası denetim `whost.update_watch` adıyla yazar. Sonuç ayrıca geçmiş satırında (`/admin/updates` → Geçmiş; geri almanın gerekçesiyle) ve denetim kaydı satırlarında (`update_install_started`, ardından `update_installed` ya da `update_failed`; otomatik kurulum yalnız sonuç satırını yazar) tutulur. Uygulama adımından restart gerçekleşene — ya da bir geri alma önceki sürümü yerine koyana — kadar agent'ın periyodik öz denetimleri ertelenir, çünkü diskteki dosyalar sürecin yüklediği dosyalar değildir; o pencereye düşen denetim koşmak yerine `memory and manifest checks deferred` (`whost.main`) satırını yazar.

**Yedekler ve güncelleme hiç çakışmaz.** Bir koşum agent'ın yeniden başlatılmasıyla biter ve yeniden başlatma, sürecin o an yaptığı yedek işini keser — kopyalayan ya da yükleyen bir yedeği, yarısı yazılmış bir geri yüklemeyi, saklama adımına gelmemiş bir zamanlanmış yedeği. Bu yüzden böyle bir iş sürerken kurulum `409 BACKUP_IN_PROGRESS` ile reddedilir; ileti her işi ve başladığı saati adlandırır (panel hata bildiriminde gösterir), kurulum iş bittikten sonra yeniden başlatılır. Rotanın yanıtıyla koşumun ilk adımı arasındaki anda başlayan yedek işi koşumu aynı iletiyle `failed` olarak bitirir; hiçbir şey değişmemiştir. Tersi yönde, koşumun ilk adımından süreci yenisiyle değiştiren yeniden başlatmaya kadar panelden ya da API'den başlatılan yedek veya geri yükleme `409 UPDATE_IN_PROGRESS` ile reddedilir; vakti gelen zamanlanmış yedek bekler ve güncellemeden sonraki ilk zamanlayıcı turunda koşar. Süren yedek işi bulan otomatik kurulum başarısızlık bildirimi gönderilmeden ertelenir ve bir sonraki günlük denetim yerine 30 dakika sonra yeniden denenir. Başka bir yolla durdurulan süreç (operatörün yeniden başlatması, `needrestart`, çökme) yedek işini yine kesebilir; sonraki açılış sahipsiz çalışma ağaçlarını arka planda siler — hizmet önce yanıt verir — ve durdurulan sürecin bitiremediği uzak hedef kopyası yedeğin satırında kopyalanmış gibi değil, başarısız olarak işaretlenir ("The remote copy did not finish: the agent stopped during the upload.").

**Koşum sırasında durdurulan agent.** `systemctl restart whost-agent` — operatörün yazdığı ya da `needrestart` gibi bir paket yöneticisi yardımcısının verdiği — koşum kurulu ağaçları takas ederken gelebilir. Koşumun adımları kendi görevinde yaşar; bu yüzden çağıranın ortadan kalkması (zamanlayıcının durdurulması, olay döngüsünün kapanması) adımları kesmez ve agent'ın kapanışı, uygulama adımına ulaşmış bir koşumu 25 sn'ye kadar — birimin 30 sn'lik durma zaman aşımının altında — bekler; `shutdown requested while an update is being applied` (`whost.update_service`) satırını yazar ve restart o kadar uzun sürer. Uygulama adımına ulaşmamış bir koşum diskte hiçbir şeyi değiştirmemiştir ve iptal edilir. Bekleme, kod takası ile manifest takası arasında doğrudan öldürülen bir süreci (SIGKILL, güç kaybı) kapsayamaz: sonraki açılışın bütünlük denetimi o çifti reddeder, kurulum sonrası denetim henüz kuyruğa alınmamıştır ve önceki sürüm `whost_agent.old`'dan (varsa `panel.old`, `whost-agent.service.old`, `venv.old` ve `requirements.txt.old` ile birlikte) elle yerine konur.

**Neden ertelenmiş restart?** Agent'ın içinden verilen bir `systemctl restart whost-agent` birimin durmasını bekler; oysa çalışan birim çağrıyı yapan sürecin ta kendisidir — systemd onu çağrının ortasında durdurur ve koşum kendi adımlarını hiç bitiremez. Çözüm: 3 s sonra, bu süreç yanıtını verdikten sonra ateşlenecek geçici bir `systemd-run` birimi zamanlamak ve restart döngüsünü systemd'ye bırakmak.

**Neden geçmiş restart'tan önce yazılır?** Aynı kök neden: kaydı yazan süreç restart'ın yerine koyduğu süreçtir ve yeni süreç koşumdan bellekte hiçbir şey taşımaz; başarı kaydını önce yazmak, restart ne yaparsa yapsın eksiksiz bir geçmiş bırakır.

**İşaretçi dosyası yok.** Yeni süreç koşumun geride bıraktığı hiçbir şeyi okumaz: kurulumun kaydı geçmiş satırıdır ve `update_marker.json` yoktur. Restart'ı izleyen tek şey, kendi birimi olarak çalışan kurulum sonrası denetimdir.

**Bir sürümün ilk açılışında nginx snippet'leri.** Panelin location snippet'ini (`/etc/nginx/snippets/whost-panel-locations.conf`) ve webmail snippet'ini (`/etc/nginx/snippets/roundcube.conf`) installer yazar; ajan bunları başka zaman yalnız hostname, duyurulan IP ya da Force SSL kaydedildiğinde (panel) veya Roundcube panelden kurulduğunda (webmail) yeniden yazar. Yalnız güncellenen bir kurulumun kurulduğu günkü location kümesinde kalmaması için her açılış iki dosyayı (webmail dosyasını web sunucusu nginx ya da nginx + Apache olduğunda) çalışan sürümün şablonlarıyla karşılaştırır ve farklı olanı şablona getirir: dosya yazılır, `nginx -t` karar verir, nginx yeniden yüklenir; nginx'in reddettiği kopyada (ya da nginx çalıştırılamıyorsa) önceki dosya geri konur. Panel yenilemesi server block'a (`sites-available/whost-panel.conf`; host adlarını ve sertifika yollarını taşır) dokunmaz, rate-limit bölge dosyasını yalnız yoksa yazar; webmail yenilemesi mevcut dosyanın adlandırdığı docroot'u ve PHP-FPM soketini korur, bunları okuyamadığı dosyayı olduğu gibi bırakır. Temiz kurulum şablonları zaten taşıdığından açılışları hiçbir şey değiştirmez. Değişiklik agent log'una `Panel nginx snippet brought to the current template` (`whost.system_router`) ve `Nginx Roundcube snippet written` (`whost.webmail_service`) olarak, reddedilen kopya `… failed nginx -t — reverted` olarak düşer.

---

## 3. Aşama İlerlemesi

Panel canlı ilerleme için bir Server-Sent Events akışına abone olur (`GET /api/v1/system/update/status/stream`; tarayıcının `EventSource`'u başlık gönderemediği için bundle-fingerprint başlığından muaf, oturum çerezi ya da imzalı anahtarla kapılı). Pipeline'ın yayımladığı aşamalar ve ilerleme yüzdeleri:

| Aşama | % | Not |
|---|---|---|
| `idle` | 0 | Süren kurulum yok (akış tek olay verir ve kapanır) |
| `backup` | 5 | agent kodunun tar'ı (`backup_before_update` kapalıysa atlanır) |
| `downloading` | 15 | vendor'dan taze token istenir (token beş dakika ve yalnız onu üreten node'da geçerlidir), sonra paket o node'dan alınır |
| `verifying` | 35 → 45 | kontrol yanıtı ve başlığa karşı SHA-256, sonra `.asc` yayın imzası, sonra boş alan denetimi |
| `extracting` | 50 | `safe_extract` üye yürüyüşü, ardından paketin taşıdığı sürüm |
| `migrating` | 60 → 65 | paket taşıyorsa `migrations/{from}_to_{to}.py` (WHost paketleri taşımaz), ardından Python bağımlılık denetimi (yalnız eksik paket varsa ortamın kopyasında pip koşumu — dakikalar sürebilir) |
| `applying` | 70 → 80 | üretildiyse ortam değişimi, atomik agent değişimi + servis birimi + requirements dosyası, sonra panel değişimi |
| `health_check` | 85 | kurulu ağacın her düz `.py` modülü bellekte derlenir, bütünlük manifesti değişimi |
| `completed` | 100 | geçmiş yazıldı, ertelenmiş restart bekliyor |
| `restarting` | 100 | kurulum sonrası denetim başlatıldı, `systemd-run` restart'ı kuyruğa alındı; akış bu aşamada kapanır |
| `rolling_back` → `rolled_back` | 0 | dosyalar değiştirildikten sonra bir adım başarısız oldu; koşumun değiştirdiği geri konur |
| `failed` | 0 | koşum hiçbir şeyi değiştirmeden bitti (ya da geri alması tamamlanamadı) |

Her olay durum anlık görüntüsüdür (`stage`, `progress`, `message`, `error`, sürümler), 2 s'de bir. Akış `completed`, `failed`, `rolled_back`, `idle` ve `restarting`'de kapanır; eski süreç yeniden başlarken tarayıcı akışın düştüğünü görür ve yeni süreç yeni `current_version` ile ilk olayını verene dek akışı 3 s'de bir yeniden açar, sonra sayfayı yeniler. O yanıt 60 s içinde gelmezse sayfa beklemeyi bırakır ve agent'ın geri gelmediğini söyler (güncelleme yine de tamamlanmış olabilir: sayfayı yenileyip sürümü kontrol edin).

---

## 4. Geri Alma (Rollback)

```mermaid
sequenceDiagram
    participant US as update_service
    participant Live as /opt/whost/agent + /var/www/whost/panel + /etc/whost/integrity.lock + whost-agent.service

    Note over US: bir uygulama adımı, kod denetimi ya da manifest değişimi başarısız oldu
    US->>US: stage = "rolling_back"
    US->>Live: venv.old yeniden venv olarak adlandırılır (yalnız bu koşum ortamı değiştirdiyse); requirements.txt ve requirements.lock .old kopyalarından geri yüklenir
    US->>Live: whost_agent.old mevcut agent ağacının üstüne geri adlandırılır, whost_agent.new silinir
    US->>Live: bütünlük manifesti .lock.old'dan geri yüklenir (yalnız bu koşum değiştirdiyse)
    US->>Live: servis birimi .old kopyasından geri yüklenir (yalnız bu koşum değiştirdiyse)
    US->>Live: panel.old panel ağacının üstüne geri adlandırılır, panel.new silinir
    US->>US: geçmiş kaydı status=rolled_back ve error_message; audit satırı update_failed
```

Geri konan sürüm çalışan sürecin açıldığı sürümdür; bu yüzden rollback hiçbir şeyi yeniden başlatmaz: operatör hata kartını agent'ın gerekçesiyle görürken servis yanıt vermeyi sürdürür.

Atomik rename kalıbı sayesinde başarısız bir kurulum ya `.new` hazırlık dizinini yerinde bırakmıştır (etkisiz, rollback siler) ya da rename'i tamamlamıştır (rollback geri çevirir). Yarım uygulanmış agent ağacı yoktur ve panel agent'ı izler: geri alınan kurulum yine önceki paneli sunar.

Rollback, başarısız koşumun kendi değiştirdiğini geri alır, başka hiçbir şeyi değil. Her uygulama adımı önce daha eski bir güncellemeden kalan `.old` kopyasını siler; bu yüzden rollback sırasında bulunan bir `.old` ağacı ya da `integrity.lock.old` yalnız kurulum başladığında çalışan sürüme ait olabilir — reddedilen ikinci bir güncelleme, çalışan sürümün altına daha eski bir sürümün manifestini, panelini ya da kodunu asla geri koymaz. Uygulama adımlarından önce reddedilen paket (sağlama, imza, açma) hiçbir şeyi değiştirmez; rollback ve restart tetiklenmez.

Kod denetiminden geçtiği hâlde açılamayan bir paket (örneğin başka bir Python için derlenmiş modül) yukarıda anlatılan kurulum sonrası denetimle geri alınır; `.old` ağaçları bir sonraki kuruluma kadar yerinde durur ve geri alma, reddedilen ağaçları tanı için `.failed` olarak yanlarında bırakır. Denetimin kendisi önceki sürümü geri getiremezse log satırları bunu söyler ve kurtarma bu kopyalardan elle yapılır. `venv.old` ağaçlarla aynı kurala uyar: her koşum, kendisi ortam üretsin ya da üretmesin, daha eski bir güncellemeden kalan kopyayı siler; böylece bir geri almanın ya da kurulum sonrası denetimin bulduğu ortam her zaman çalışan sürümün başlatıldığı ortamdır.

Geri alma `/etc/whost/agent.conf` dosyasına ve `/var/lib/whost/` altındaki verilere dokunmaz. Bunun görünür iz bıraktığı tek durum şudur: whitelabel logolarını dosya olarak tutan bir sürüm, `agent.conf` içinde hâlâ satır içi duran logoları ilk açılışında `/var/lib/whost/branding/` altına taşır ([configuration.tr.md › whitelabel](configuration.tr.md#whitelabel)). Bu sürümün ardından dosyalardan önceki bir sürüme geçilirse — böyle bir açılıştan sonra kurulum sonrası denetimin önceki sürümü geri koyması ya da elle sürüm düşürme — panel, `/var/lib/whost/branding-legacy-<zaman>.yaml` dosyasında saklanan özgün değerler `agent.conf`'un `whitelabel` bloğuna geri kopyalanıp agent yeniden başlatılana kadar özel logo göstermez.

---

## 5. Otomatik Güncelleme Zamanlayıcısı

Zamanlayıcı, ajanla birlikte başlayıp duran bir arka plan görevidir:

| Konu | Davranış |
|---|---|
| Kadans | kontroller arası 24 saat, güncelleme servisinin yükünü dağıtmak için 30 dakikaya kadar jitter |
| İlk tik | her agent açılışından 5 dk sonra (her güncellemeyi ya da restart'ı bir satıcı kontrolü izler) |
| Panel bandı | Yönetici kabuğundaki "Güncelleme mevcut" bandı `GET /api/v1/system/update/status`'u — son kontrolün saklanan sonucunu — okur; bu yüzden bir yönetici sayfasının yüklenmesi satıcıyı hiç sorgulamaz, yalnız zamanlayıcı ve güncellemeler sayfasının kendi kontrolü sorgular |
| Tik başına adımlar | (0) güncelleme öncesi arşiv klasörlerinden (`/var/whost/backups/update_<zaman>/`) en yeni üçü dışındakileri sil, (1) `check_for_updates()` — canlı satıcı sorgusu, (2) OS paket önbelleğini tazele (`os_packages_check_enabled` açıkken), (3) aşağıdaki bildirimleri gönder (güncelleme mevcut bildirimi yalnız `notify_available` açıkken), (4) `auto_update` açıksa ve sürüm farkı `auto_update_type` ile eşleşiyorsa otomatik kur — yedek, geri yükleme ya da zamanlanmış yedek sürerken ertelenir ve 30 dk sonra yeniden denenir |
| Otomatik kurulum politikaları | `security` (yalnız satıcının zorunlu işaretlediği ve yalnız `allow_vendor_critical_override` ile; bu izin olmadan bu mod yalnız bildirir), `patch` (aynı X.Y: bir patch adımı), `minor` (aynı X: bir minor ya da patch adımı), `all` (yeni bir major dahil her adım). Bir beta (`X.Y.Z-beta.N`) kendi `X.Y.Z`'sine göre ölçülür: aynı numaranın sonraki betası ve kararlı sürümü patch adımıdır; başka bir ek taşıyan sürüm kendiliğinden hiç kurulmaz. Varsayılan (`security`) hiçbir betayı kendiliğinden kurmaz, çünkü beta sürümü zorunlu olamaz — yönetici bildirim alır ve panelden kurar |
| Satıcı kritik override | `allow_vendor_critical_override` — açıkken satıcının "zorunlu" işaretlediği güncellemeler `auto_update_type`'tan bağımsız otomatik kurulur |
| Bildirim tekilleştirme | `/var/whost/update_notify_state.json` içinde kalıcı — aynı üst sürüm restart'lar boyunca iki kez bildirilmez; damga bir otomatik kurulumdan sonra temizlenir. `notify_available` kapalıyken hiçbir şey gönderilmez ve damga olduğu yerde kalır; anahtar yeniden açılınca hâlâ sunulan sürüm bir sonraki kontrolde bildirilir |

Zamanlayıcı bilinçli olarak tembeldir: her korumalı endpoint her istekte zaten lisans denetiminden geçtiği için 24 saatte tek kontrol yeter. Üst kaynağı dakikada bir yoklamaya gerek yoktur.

---

## 6. OS Paket Kanalı

Sistem düzeyi güncellemeler için ayrı, daha basit bir pipeline:

```mermaid
sequenceDiagram
    participant Operator
    participant API as POST /system/packages/upgrade
    participant Apt as apt-get / dnf
    participant Status as GET /system/packages/upgrade/status

    Operator->>API: {security_only, package_names?}
    API->>API: paket adlarını doğrula (model regex'i; bekleyen listede olmayan ad → 422)
    API->>API: tek yükseltme yuvasını al (bellek içi; süren iş 200 ile canlı durumunu döner)
    API->>Apt: arka plan görevi (argv listesi, shell yok)
    API-->>Operator: 200 {stage: "running", ...} (hemen döner, polling)
    loop bitene kadar
        Operator->>Status: GET status
        Status-->>Operator: {stage, progress, message, upgraded, output_tail}
    end
    Apt->>Apt: geçmişi yaz /var/whost/os_packages_history.json (son 100 deneme), bekleyen önbelleği tazele
```

**Doğrulama:** paket adları `^[a-z0-9][a-z0-9.+\-]*$` (apt'nin kendi ad biçimi) ve bekleyen listeye karşı kontrol edilir. Uyumsuzluk apt'ye ulaşmadan 422 döner. Shell yoktur — agent apt'yi argv listesiyle çağırır.

**Yalnız güvenlik modu:** Debian ve Ubuntu'da `security_only=true` paket listesini `apt-get --just-print upgrade` çıktısından, kaynak dağıtım adı (suite) `-security` ile biten paketleri alarak kurar; RHEL ailesinde dnf'in kendi güvenlik meta verisini okuyan `dnf upgrade --security` çalışır. Bekleyen güvenlik güncellemesi yoksa iş hata olarak değil, "No security updates available." iletisiyle `completed` olarak biter.

**Eşzamanlılık:** aynı anda tek yükseltme işi; biri sürerken ikinci `POST` yenisini başlatmak yerine canlı durumu döner. Uç adres başına saatte iki başlatma kabul eder (üçüncüsü `429` alır). İş sürerken `GET /system/packages/updates` dpkg kilidi altında apt çalıştırmak yerine önbellekteki listeyi verir.

**Liste okumanın maliyeti:** `GET /system/packages/updates` her çağrıda `apt-get update` ve bir deneme yükseltmesi çalıştırır (doksan bekleyen paketli bir sunucuda yaklaşık on saniye); panel bunu Sistem Güncellemeleri sayfası (`/admin/updates`) açıldığında — hangi sekme açık olursa olsun; OS Paketleri sekmesinin etiketi sayıyı taşır — ve en çok beş dakikada bir okur; topbar rozeti önbellekteki `/system/packages/summary`'yi okur.

---

## 7. Bildirim Yüzeyi

| Olay | Alıcı | Kanal | Tekilleştirme |
|---|---|---|---|
| Güncelleme mevcut (zamanlayıcı kontrolü; yalnız `notify_available` açıkken) | admin | bildirim ayarlarına göre panel bildirimi + e-posta (bu olayın olay matrisinde kendi satırı var) | `last_notified_version` |
| Bekleyen OS güvenlik güncellemeleri (zamanlayıcı tazelemesi; yalnız sayı arttığında) | admin | bildirim ayarlarına göre panel bildirimi + e-posta (`update` kategorisi) | `last_os_security_count` |
| Otomatik kurulum tamamlandı (yalnız `notify_installed` açıkken) | admin | bildirim ayarlarına göre panel bildirimi + e-posta (`update` kategorisi) | ardından damga temizlenir |
| Otomatik kurulum başarısız (iki bildirim anahtarından bağımsız) | admin | panel bildirimi + e-posta (kritik: panel ya da e-posta kanalı açıksa gönderilir) | — |

Panelden başlatılan bir kurulum başlarken bir audit satırı (`update_install_started`) ve sonucu için bir satır (`update_installed`, ya da gerekçesi ve önceki sürümün geri konup konmadığıyla `update_failed`) artı geçmiş kaydı yazar; bildirim göndermez — onu başlatan operatör akışı zaten izlemektedir. Kurulum ucu adres başına saatte altı başlatma kabul eder; reddedilen başlatma `429` ile ve `retry_after` alanında o pencerede kalan saniyeyle yanıtlanır.

---

## 8. Ayarlar

`/etc/whost/update_settings.json` (root, `0600`) agent'ın okuduğu tek kaynaktır; `agent.conf`'ta `update:` bloğu yoktur:

```json
{
  "channel": "stable",
  "auto_update": true,
  "auto_update_type": "security",
  "backup_before_update": true,
  "notify_available": true,
  "notify_installed": true,
  "os_packages_check_enabled": true,
  "os_packages_notify_security": true,
  "allow_vendor_critical_override": true
}
```

Kurucu dosyayı yeni bir sunucuda bir kez ve yalnız kanalla yazar: `-beta.N` bir sürümden kurulan sunucu `beta`, kararlı bir sürümden kurulan `stable` kanalında başlar. Diğer alanlar yukarıdaki varsayılanda kalır; var olan dosyaya dokunulmaz — kurucuyu yeniden çalıştırmak yöneticinin seçimini sıfırlamaz. Dosya yoksa yukarıdaki varsayılanlar geçerlidir.

Operatör değişiklikleri panelden yapılır (`/admin/updates` → Ayarlar sekmesi); sayfa değişen alanı `PUT /api/v1/system/update/settings` ile yazar. Hiçbir şeyi değiştirmeyen bir kayıt ne dosyayı ne bir audit satırını yazar; gerçek bir değişikliğin audit satırı yalnız değişen alanları adlandırır. Var olan ama ayrıştırılamayan bir dosya loglanır ve onarılana kadar `auto_update: false` ile varsayılanlar olarak okunur; panelden yapılan bir kayıt bu durumu dosyaya yazar, bu yüzden otomatik güncellemeler yönetici yeniden açana kadar kapalı kalır. Varsayılanlar dostçadır: yeni kurulmuş bir sunucu satıcının kritik işaretlediği güvenlik yamalarını kendiliğinden uygular (her yamanın açık onay gerektirdiği uyum ortamları için yapılandırılabilir); hiçbir beta işaretlenmez, bu yüzden bu varsayılanlar hiçbir betayı kendiliğinden kurmaz (§5).

---

## 9. API Uçları ve Dosyalar

| Konu | Uç ya da dosya | Not |
|---|---|---|
| WHost yazılım güncellemeleri | `/system/update/*` | kontrol, sürüm notları, kurulum, durum ve ilerleme akışı, geçmiş, ayarlar |
| OS paket güncellemeleri | `/system/packages/*` | özet, bekleyen güncellemeler, yükseltme, yükseltme durumu, yükseltme geçmişi |
| Geçmiş | `/var/whost/update_history.json` (WHost) ve `/var/whost/os_packages_history.json` (OS) | atomik yazılan JSON dizileri |
| Ayarlar | `/etc/whost/update_settings.json` | tek kaynak |

Kurulum ucu ve OS paket yükseltme ucu etkin bir lisans (ya da hâlâ ek süre penceresindeki bir lisans) ister; aksi hâlde `403` ile `LICENSE_NOT_ACTIVATED` (hiç etkinleştirilmemiş) ya da `LICENSE_SUSPENDED` (diğer her durum) döner. Bir güncellemeyi yeniden denemeden önce lisansı kurtarın.
