# Çekirdek Yükseltmesini Atlatma

https://dev.wisecp.com/tr/cekirdek-yukseltmesini-atlatma

Yükseltme koşusunun adım adım ne yaptığı, neyi geri alabildiği ve öncesinde ile sonrasında yapılacak kontroller.

## Genel Bakış

Bir yükseltme, zaman bütçesi olan bir adımlar dizisi ve üzerine yazdığı her şeyin defteri. Son yazma ile sürüm damgası arasında bir sağlık kapısı durur.

## Ön Koşullar

- Bozabileceğiniz ikinci bir sistem. Müşterilere hizmet veren sistem ya da onunla aynı veritabanını kullanan bir kopya olmasın.
- Koşudan önce alınmış ve doğrulanmış gerçek bir yedek.
- Başlamadan önce not edilmiş, `coremio/VERSION` dosyasındaki güncel sürüm.
- [Çekirdeğe Dokunmadan Çalışma](https://dev.wisecp.com/tr/cekirdege-dokunmadan-calisma): aşağıdaki her kontrol, kodunuzun bir genişleme noktasının arkasında olduğunu varsayar.

## Yapı

### Koşu

Bir sürüm, sabit sırayla beş adım. Zincirli bir yükseltme her sürümü tam ve sırayla, bir öncekinin koduyla uygular.

| Adım | Ne yapar | Geri alınabilir mi? |
| --- | --- | --- |
| `backup` | İsteğe bağlı. Hiçbir şeye dokunulmadan önce veritabanı, dosyalar ve yüklemeler | Geçerli değil, yalnız okur |
| `download` | Paketi çalışma dizinine indirir | Evet, çalışma dizininin dışında hiçbir şey değişmedi |
| `extract` | Dilim başına bir grup girdi olacak şekilde paketi açar | Evet, aynı sebeple |
| `database` | Sürümün şema ifadelerini bir kez koşturur. Zaten uygulanmış ifadeler atlanır, sırası gelmemişler dosyanın sonunda tekrarlanır | **Hayır.** İleri yönlü olarak kaydedilir ve operatöre adıyla söylenir |
| `configuration` | Sürümün yapılandırma betiğini koşturur, sonra dil dosyalarını ve bildirim şablonlarını birleştirir | Birleştirmeler evet, betik **hayır**: keyfi kodun kopyası alınamaz |
| `apply` | Paketin dosyalarını sistemin üzerine kopyalar, sonra silme bildirimi, sağlık kapısı ve sürüm damgası | Evet, defterden, bir kez |
| `finish` | Çalışma dizinini süpürür | Geçerli değil |

Her adım zaman bütçesi dolana kadar çalışır, nereye geldiğini bir imlece yazar ve döner. Bir web isteği, yapılandırılmış çalışma süresi sınırının 5 ile 45 saniye arasına kısılmış yüzde altmışını alır. Dakikalık cron'un kullandığı komut satırı dilimi 300 saniye alır. Cron işleyicisi kendi 1800 saniyelik tavanının 1440. saniyesinden sonra yeni dilim başlatmaz.

### Koşu Öncesi Kopya Defteri

`coremio/storage/updates-preimage` altında koşu başına bir dizin. Çalışma dizininin dışında durur, çünkü koşudan uzun yaşar. Her üzerine yazma ve bildirimle yapılan her silme önce buraya kopyalanır. Saklanan kopya koşu öncesi hâli taşır.

| Girdi | İçinde ne var | Onu ne okur |
| --- | --- | --- |
| `meta.json` | Geldiği sürüm, uygulanan sürümler, yeniden uygulama olup olmadığı, öncesindeki durum | Sunulup sunulmayacağına karar veren geri alma butonu |
| `files/` | Üzerine yazılan ya da silinen her dosyanın koşu öncesi kopyası | Hepsini geri kopyalayan geri yükleme |
| `added.list` | Koşunun oluşturduğu, daha önce var olmayan yollar | Onları silen geri yükleme (bir eklemeyi geri almak kaldırmaktır) |
| `changed.list` | İçeriği gerçekten değişen dosyalar, sağlama toplamına göre | Tam olarak bu listeyi ve başka hiçbir şeyi lint'leyen sağlık kapısı |
| `forward.list` | Geri alınamayanlar: şema migration'ı, sürümün yapılandırma betiği | Onları operatöre adıyla söyleyen geri alma özeti |
| `baseline.json`, `gate.json`, `restored.json` | Öncesindeki ve sonrasındaki sağlık ölçümü, bir geri yüklemenin kanıtı | Kapının ne gördüğünü bilmeniz gerektiğinde siz |

### Sağlık Kapısı

Sağlık iki kez ölçülür: uygulama adımının ilk baytı yazılmadan önce bir taban ölçüm, sonra kapı. Yargı göreli olduğu için taban ölçüm zaten bozuksa hiçbir şey geri alınmaz.

Ucuzdan pahalıya üç soru sorulur. Değişen PHP dosyaları hâlâ ayrıştırılıyor mu? Taze bir süreç açılıp veritabanına ulaşıyor mu? Sistem kendi anasayfasını cevaplıyor mu?

| Alan | Anlamı | Kapıya etkisi |
| --- | --- | --- |
| `errors` | Bir kontrol koştu ve düştü | Geri almayı tetikler ve koşu sağlık kontrolü hatasıyla düşer |
| `skipped` | Bir kontrol hiç koşamadı | Yargı yok. Kanıt dosyasına yazılır, dışında dikkate alınmaz |
| `http` | Boolean değil bir durum: ok, ulaşılamadı ya da hata | Taban ölçümle karşılaştırılır, inanılmadan önce ikinci kez sorulur |

## Adım Adım

### Öncesinde

1. Bağımlılık listenizi hafızaya değil bir dosyaya yazın. Dinlediğiniz her kanca adı. Kendi dizininizin dışından çağırdığınız her sınıf ve metot. Okuduğunuz her çekirdek tablosu, kolonu ve yapılandırma anahtarı.
2. Kendi ağacınızda çekirdek yollarını arayın ve hiçbirini düzenlemediğinizi doğrulayın. Defter sizin sürümünüzü değil bir öncekini geri getirir.
3. Modülünüz güncellenebilir olacaksa bir `manifest.json` taşıdığını kontrol edin. Manifest yoksa güncelleyici o dizine hiç dokunmaz.
4. Yükseltmenin tamamını önce kopyada, hem cron'dan hem panelden çalıştırın.

### Sonrasında

1. Tek bir sayfa açmadan önce uyumluluk probunuzu çalıştırın. Çağırdığınız her sınıf ve metot hâlâ var mı?
2. Bağımlı olduğunuz her kancayı çalıştırın ve kanaryanızın bir isabet kaydettiğini doğrulayın. Kanca adlarının çalışma zamanı kaydı yoktur.
3. Hata günlüğünü okuyun. Bir dinleyicinin içindeki istisna yakalanır, loglanır ve yutulur. Bozuk bir dinleyici hiç ateşlenmemiş bir kancaya benzer.
4. Kodunuzun okuduğu yapılandırma anahtarlarını kontrol edin. Uygulama adımı var olan bir yapılandırma dosyasını atlar, modül güncelleyici de `config.php` dosyanızı korur. Yeni anahtarlar sizin için oluşturulmaz.
5. Modülünüz tablo sahibiyse ya da kolon eklediyse kendi şema kapınızı yeniden çalıştırın. Çekirdek yükseltmesi onlardan haberdar değil.
6. Operatöre geri alma penceresinin bir koşu ve bir kez olduğunu söyleyin.

### Bir Genişleme Noktası Yer Değiştirdiğinde

| Belirti | Kontrol | Genellikle şu demektir |
| --- | --- | --- |
| Dinleyiciniz artık çalışmıyor ve hiçbir şey loglanmıyor | Kanca kataloğunda önce adı, sonra yakın adları arayın | Kanca yeniden adlandırıldı ya da tetiklendiği nokta kaldırıldı. Kaydınız hâlâ geçerli ve hiçbir şeye bağlı |
| Dinleyiciniz çalışıyor ama değişiklik dikkate alınmıyor | Noktanın değerle mi referansla mı ateşlendiği | Kanca referanslı olmuş. Dönüş değeriniz atılıyor, çağıran argümanın yerinde değiştirilmesini bekliyor |
| Ölümcül hata: tanımsız metot çağrısı | Loga değil kaynağa bakın | Sınıf adını korumuş, metot korumamış. Çağrıyı kapıya alın, hata fırlatmak yerine yeteneği kapatın |
| Çok az ya da çok fazla argüman | Kancanın sayfasındaki parametre tablosu | Tetiklendiği noktanın argüman listesi değişti. Yalnız kullandığınız parametreleri tanımlayın, kalanına varsayılan verin |
| Admin sayfanız bulunamadı dönüyor | Rotalardan önce modül tipi | Sessizce reddedilen bir admin alanı kaydı. Ya da bir çekirdek rotasıyla çakışan bir slug |
| Cron'dan çalışıyor, panelden ölüyor | Her iki bağlamda `function_exists('exec')` | **Yükseltme regresyonu değildir.** Paylaşımlı hosting, süreç fonksiyonlarını web havuzunda kapatırken komut satırı ikilisinde açık tutar |

### Kendi Güncellemenizi Yayınlama

1. Modül ya da tema dizinine bir `manifest.json` koyun.
2. Sürümü orada tutun, başka hiçbir yerde. `config.php` içindeki sürüm ilk kurulumda donar, çünkü bir güncelleme o dosyayı hiç yazmaz.
3. Yayımladıktan sonra bir sürüm numarasını asla yeniden kullanmayın ya da düzenlemeyin. Her sistemin karşılaştırdığı kimlik odur.
4. Müşterinin diskinde, göndermediğiniz iki şey yaşamaya devam eder. Zaten var olan her yerde atlanan `config.php` dosyanız. Bir de paketten çıkardığınız her dosya: bir güncelleme üzerine yazar ama silmez.
5. Kendi araçlarınızda da manifest'i en son yazın. Yarım kalmış bir uygulama, diskte tam olmayan bir sürümü asla iddia etmemeli.

## Referans

### Yükseltme API'si

```php
const VERSION_STEPS  = ['download', 'extract', 'database', 'configuration', 'apply'];
const PREIMAGE_DIR   = 'updates-preimage';   // coremio/storage/ altında
const PREIMAGE_KEEP  = 3;                    // saklanan koşu; yeni koşu açılınca budanır
const LINT_SECONDS   = 60.0;                 // kapı lint bütçesi, dosya sayısı değil süre
const LINT_MAX_FILES = 2000;                 // süre bütçesinin arkasındaki emniyet freni
const HTTP_SETTLE_SECONDS = 5;               // sormadan önce ve geri yükledikten sonra beklenir

public static function open(array $opts, string $workerId): int;
public static function tick(int $runId, string $workerId): array;
public static function health_probe(): array;
public static function restorable(): array;
public static function restore_last(): array;
public static function parked(array $run): bool;
```

```php
public static function check_state(): array;
public static function check_new_version(): array;
public static function next_versions(string|int $ver = 0): array;
public static function run_active(string $kind = 'core'): array;
public static function last_completed_run(string $kind = 'core'): array;
public static function run_get(int $id): array;
public static function sc_state(): array;

// coremio/helpers/license.php - sistemin şu an kendini hangi sürüm saydığı.
public static function version($realtime = false): string;
```

- **UpdateRunner::health_probe()**: Aynı üç sorunun bir yükseltmenin dışında sorulmuş hâli; değişen dosya listesi boş olduğu için lint geçişi kendini atlar. `['ok' => bool, 'errors' => string[], 'skipped' => string[], 'http' => string]` döner. Kendi probunuzun kopyalayacağı şekil budur.
- **UpdateRunner::restorable()**: Geri alınabilecek koşuyu ya da boş bir dizi döner. Aktif koşu varken, tamamlanmış koşu yokken, defterde meta veri yokken ya da bir geri yükleme zaten yapılmışken boştur. Geri alma koşu başına bir kez sunulur.
- **Updates::run_active()**: Bir yükseltme sürerken boş olmayan bir dizi. Kendi zamanlanmış işinizin başında kontrol etmeye değer: süren bir koşu, dosya ağacının karışık olduğu anlamına gelir.
- **License::version()**: Koşucunun en son yazdığı damgayı okur, yani yalnız tamamlanmış bir sürümü bildirir. `version_compare()` ile karşılaştırın: metin olarak iki basamaklı bir ara sürüm, tek basamaklının altında sıralanır.

### Güncelleme Manifesti

Modül sınıfının yanında, temada ise tema tanımının yerine değil yanına konur.

```json
{"type":"marketplace","name":"AcmeBilling","version":"1.2.0","last_updated":"2026-07-28"}
```

| Alan | Zorunlu | Ne yapar |
| --- | --- | --- |
| `type` | Her zaman | Bu dizinin sorumlusu hangi yayıncı. Bilinmeyen bir değer manifest'i yarım okutmak yerine tümüyle geçersiz kılar |
| `name` | Mağaza ürünlerinde | Ürün anahtarı. Dizin adı kullanılmaz; onu siz seçersiniz |
| `id` | Marketplace ilanlarında | İlan kimliği, aynı sebeple: iki geliştirici aynı dizin adını seçebilir |
| `version` | Her zaman | Kurulu sürüm. Karşılaştırmayı yalnız ve yalnız bu yapar |
| `last_updated` | İsteğe bağlı | Operatöre gösterilir. Hiçbir kararda yer almaz |

> **Tespit otomatiktir, kurulum değil**
> 
> Günlük görev yeni sürüm arar ve operatöre bildirim gönderir. Modül güncellemesi kuran zamanlanmış bir görev yok.

## Örnek

Modülünüzün gönderebileceği bir uyumluluk probu.

```php
namespace WISECP\Modules\Addons\Acme\Src;

class Compat
{
    // Bağımlılık yüzeyi, veri olarak. Bu modülün kendi dizini dışında dokunduğu her şey
    // burada listelenir; böylece prob, yeniden yazım değil listenin kendisi olur.
    private const NEEDS_METHOD = [
        'Checkout::payment_methods',
        'Services::get',
        'Invoices::create',
    ];

    private const NEEDS_HOOK = [
        'filter:order.cart_totals',
        'action:order.checkout_completed',
    ];

    private const CORE_MIN = '5.0.0';
    private const CORE_MAX = '6.0.0';   // hariç: ana sürüm sıçraması tercihe bağlıdır, varsayılmaz

    /** @return array{ok:bool,errors:string[],skipped:string[],version:string} */
    public static function check(): array
    {
        $errors = $skipped = [];
        $core   = \License::version();

        if (version_compare($core, self::CORE_MIN, '<'))
            $errors[] = 'core ' . $core . ' is below the minimum ' . self::CORE_MIN;

        if (version_compare($core, self::CORE_MAX, '>='))
            $skipped[] = 'core ' . $core . ' is newer than this module was tested against';

        foreach (self::NEEDS_METHOD as $ref) {
            [$class, $method] = explode('::', $ref, 2);
            if (!class_exists($class))            $errors[] = 'missing class: ' . $class;
            elseif (!method_exists($class, $method)) $errors[] = 'missing method: ' . $ref;
        }

        // Sonuç değil, yetenek sorusu. FPM altında bunlar genelde kapalıdır ama aynı
        // sunucunun CLI'ı onları açık tutar; "soramadım" asla "düştü" diye okunmamalı.
        if (!function_exists('exec')) $skipped[] = 'subprocess probes (exec unavailable)';

        foreach (self::NEEDS_HOOK as $hook)
            if (!self::hook_seen($hook)) $skipped[] = 'hook not observed yet: ' . $hook;

        return ['ok' => !$errors, 'errors' => $errors, 'skipped' => $skipped, 'version' => $core];
    }

    /*
     * Kanca ADLARININ çalışma zamanı kaydı yoktur: kancalar beyan edilmez, ateşlenir ve
     * deposu özeldir. Dolayısıyla tek dürüst cevap gözleme dayanır: aşağıdaki kanarya,
     * nokta gerçekten ateşlendiğinde bir zaman damgası kaydeder ve "görülmedi" atlanandır,
     * asla hata değil, çünkü o noktaya henüz hiç ulaşılmamış olabilir.
     */
    public static function canary(string $hook): void
    {
        $seen = (array) \Utility::jdecode((string) \Config::getd('acme_hook_seen'), true);
        $seen[$hook] = time();

        \Config::setd('acme_hook_seen', \Utility::jencode($seen));
    }

    private static function hook_seen(string $hook): bool
    {
        $seen = (array) \Utility::jdecode((string) \Config::getd('acme_hook_seen'), true);

        return (int) ($seen[$hook] ?? 0) > 0;
    }
}
```

Kanarya, izlediği dinleyicinin yanına ve onu öne alan bir öncelikle kaydedilir.

```php
use WISECP\Modules\Addons\Acme\Src\Compat;

include_once __DIR__ . DS . 'src' . DS . 'Compat.php';

// Öncelik 1: asıl dinleyicinin önünde, böylece gözlem ondan bağımsız olur.
Hook::add('filter:order.cart_totals', 1, function (&$summary, $items, $subtotal) {
    Compat::canary('filter:order.cart_totals');
});

// Modülün kendisinin arkasında durduğu kapı. Uyumsuz bir çekirdek, birinin ödeme
// akışının içinde hata fırlatmak yerine özelliği devre dışı bırakır.
$acme_compat = Compat::check();

if ($acme_compat['ok']) {
    Hook::add('filter:order.cart_totals', 20, function (&$summary, $items, $subtotal) {
        // ... asıl iş
    });
}
else {
    Hook::add('ui:admin.body.end', 90, function () use ($acme_compat) {
        return '<!-- acme disabled: ' . htmlspecialchars(implode('; ', $acme_compat['errors'])) . ' -->';
    });
}
```

Koşu biter bitmez kopya üzerinde elle çalıştırmaya değer iki komut.

```bash
# Kapının gerçekte ne gördüğü, ölçemedikleri dahil.
cat coremio/storage/updates-preimage/*/gate.json

# Koşudan bu yana çıkan hatalar; yutulmuş bir dinleyici istisnasının tek izi burasıdır.
php coremio/errlog.php list --limit=20
```

## Tuzaklar

> **Cron'dan geçer, panelden ölümcül hata verir**
> 
> Paylaşımlı hosting havuzları süreç fonksiyonlarını web yapılandırmasında kapatır, aynı sunucudaki komut satırı ikilisi ise açık tutar. Kapatılmış bir fonksiyon uyarı değil hata üretir, susturma operatörü de işe yaramaz. Alt süreç açmadan önce yeteneği kontrol edin. Eksik yetenek düştü değil atlandı sayılır.

> **Dosyalar geri döner, şema dönmez**
> 
> Defteri geri yüklemek üzerine yazılan her dosyayı geri koyar ve koşunun oluşturduğu her şeyi siler. Ama migration şemayı çoktan taşımış, yapılandırma betiği çoktan çalışmıştır. Modülünüz bir çekirdek kolonunu okuyorsa, sürüm numarasını değil kolonun varlığını kontrol edin.

> **Yarı uygulanmış çekirdek gerçek bir durum**
> 
> Dosya ağacı dakikalarca iki sürümün karışımı olabilir. O pencerede başlayan zamanlanmış bir iş, bir sınıfı eski sürümden diğerini yenisinden yükleyebilir.

> **Geri alma bir koşu, bir kez ve sonuncusu için**
> 
> Buton yalnız en son tamamlanmış koşu için ve hiçbir koşu sürmezken görünür. Bir kez kullanıldığında kaybolur. Daha eskisi tam yedeğin işi.

## İlgili Makaleler

- [Güncellemeye Dayanıklı Çalışma İlkeleri](https://dev.wisecp.com/tr/guncellemeye-dayanikli-calisma-ilkeleri)
- [Çekirdeğe Dokunmadan Çalışma](https://dev.wisecp.com/tr/cekirdege-dokunmadan-calisma)
- [Modül Güncellemesi Yayınlama](https://dev.wisecp.com/tr/modul-guncellemesi-yayinlama)
- [Modülü Dağıtıma Hazırlama](https://dev.wisecp.com/tr/modulu-dagitima-hazirlama)
- [Hata Ayıklama ve Loglar](https://dev.wisecp.com/tr/hata-ayiklama-ve-loglar)
- [Zamanlanmış Görev Ekleme](https://dev.wisecp.com/tr/zamanlanmis-gorev-ekleme)
