Bunny Storage Zone — Tam Yol Haritası

Adım Adım Uygulama Rehberi + Kriz Yönetimi

12 Adım ~3 Gün Kriz Planı Dahil v3 - Kapsamlı

KESİNLEŞEN KARARLAR

HİBRİT MOD Local + Bunny
LOCAL MASTER Dosyalar silinmez
ŞİFRELEME AYNI enc.bin local'de
BATCH MİGRATİON Tam gaz yükle

İçindekiler

1 Git Backup Al KRİTİK - İLK ADIM

Herhangi bir kod değişikliğinden ÖNCE mutlaka backup al!

# 1. Mevcut durumu kontrol et
cd /var/www/vhosts/muzibu.com/httpdocs
git status
git log --oneline -5

# 2. Varsa uncommitted değişiklikleri commit et
git add .
git commit -m "🔧 CHECKPOINT: Before Bunny Storage integration"

# 3. Backup branch oluştur
git branch backup-before-bunny-$(date +%Y%m%d)
git push origin backup-before-bunny-$(date +%Y%m%d)

# 4. Yeni feature branch oluştur
git checkout -b feature/bunny-storage-integration
ÖNEMLİ
  • • Backup branch'i PUSH et (remote'da da olsun)
  • • Branch adını not al: backup-before-bunny-YYYYMMDD
  • • Sorun çıkarsa bu branch'e dönülecek

2 Bunny Storage Zone Oluştur

a

Bunny Panel'e Git

https://panel.bunny.net → Storage → Add Storage Zone

b

Storage Zone Ayarları

Zone Name:muzibu-audio
Main Region:Frankfurt (DE) - Türkiye'ye yakın
Replication:None (tek bölge yeterli, local backup var)
c

API Key Al

Storage Zone oluştuktan sonra → FTP & API Access → Password

Bu API key'i güvenli bir yere not al. .env'e eklenecek.
d

Klasör Yapısı Oluştur

Bunny File Manager'dan veya API ile:

muzibu-audio/
├── hls/           # HLS dosyaları buraya
└── songs/
    ├── mp3_128/   # 128k fallback
    └── mp3_64/    # 64k fallback

3 Pull Zone'u Storage Zone'a Bağla

Mevcut muzibuweb Pull Zone'u Storage Zone'a bağla:

Seçenek A: Yeni Pull Zone (Önerilen)

  1. 1. Bunny Panel → CDN → Add Pull Zone
  2. 2. Name: muzibu-audio-cdn
  3. 3. Origin Type: Storage Zone seç
  4. 4. Storage Zone: muzibu-audio seç
  5. 5. Create

Bu şekilde audio dosyaları için ayrı CDN URL olur: muzibu-audio-cdn.b-cdn.net

Seçenek B: Mevcut Pull Zone'a Linked

  1. 1. muzibuweb Pull Zone → General → Linked Storage Zone
  2. 2. muzibu-audio seç
  3. 3. URL prefix: /bunny-audio/
Sonuç URL'leri

Seçenek A: https://muzibu-audio-cdn.b-cdn.net/hls/123/master.m3u8
Seçenek B: https://muzibuweb.b-cdn.net/bunny-audio/hls/123/master.m3u8

4 Kod Değişiklikleri (8 Dosya + 3 Yeni)

YENİ app/Services/Bunny/BunnyStorageService.php
<?php

namespace App\Services\Bunny;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class BunnyStorageService
{
    private $apiKey;
    private $storageZone;
    private $region;
    private $baseUrl;

    public function __construct()
    {
        $this->apiKey = config('services.bunny.api_key');
        $this->storageZone = config('services.bunny.storage_zone');
        $this->region = config('services.bunny.region', 'de');

        // Frankfurt: storage.bunnycdn.com, diğer bölgeler: {region}.storage.bunnycdn.com
        $this->baseUrl = $this->region === 'de'
            ? 'https://storage.bunnycdn.com'
            : "https://{$this->region}.storage.bunnycdn.com";
    }

    /**
     * Tek dosya yükle
     */
    public function upload($localPath, $remotePath): bool
    {
        if (!file_exists($localPath)) {
            Log::error("Bunny upload failed: File not found", ['path' => $localPath]);
            return false;
        }

        $url = "{$this->baseUrl}/{$this->storageZone}/{$remotePath}";

        $response = Http::withHeaders([
            'AccessKey' => $this->apiKey,
            'Content-Type' => 'application/octet-stream',
        ])->withBody(
            file_get_contents($localPath),
            'application/octet-stream'
        )->put($url);

        if ($response->successful()) {
            Log::info("Bunny upload success", ['remote' => $remotePath]);
            return true;
        }

        Log::error("Bunny upload failed", [
            'remote' => $remotePath,
            'status' => $response->status(),
            'body' => $response->body()
        ]);
        return false;
    }

    /**
     * Klasör yükle (enc.bin hariç!)
     */
    public function uploadDirectory($localDir, $remoteDir, $exclude = ['enc.bin', 'enc.keyinfo']): bool
    {
        $files = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($localDir)
        );

        $success = true;
        foreach ($files as $file) {
            if ($file->isDir()) continue;

            $filename = $file->getFilename();

            // Şifreleme dosyalarını ATLA!
            if (in_array($filename, $exclude)) {
                Log::debug("Bunny: Skipping excluded file", ['file' => $filename]);
                continue;
            }

            $relativePath = str_replace($localDir . '/', '', $file->getPathname());
            $remotePath = $remoteDir . '/' . $relativePath;

            if (!$this->upload($file->getPathname(), $remotePath)) {
                $success = false;
            }
        }

        return $success;
    }

    /**
     * Dosya sil
     */
    public function delete($remotePath): bool
    {
        $url = "{$this->baseUrl}/{$this->storageZone}/{$remotePath}";

        $response = Http::withHeaders([
            'AccessKey' => $this->apiKey,
        ])->delete($url);

        return $response->successful();
    }

    /**
     * Klasör sil (recursive)
     */
    public function deleteDirectory($remoteDir): bool
    {
        // Bunny API: klasör sonuna / ekle
        $url = "{$this->baseUrl}/{$this->storageZone}/{$remoteDir}/";

        $response = Http::withHeaders([
            'AccessKey' => $this->apiKey,
        ])->delete($url);

        return $response->successful();
    }

    /**
     * CDN URL döndür
     */
    public function getCdnUrl($remotePath): string
    {
        return config('services.bunny.cdn_url') . '/' . ltrim($remotePath, '/');
    }
}

Değişecek Dosyalar Özeti

Dosya Değişiklik
ConvertToHLSJob.php HLS sonrası Bunny'e yükle (enc.bin hariç)
HLSService.php MP3 fallback'ları Bunny'e yükle
SongObserver.php forceDelete'te Bunny'den de sil
SongStreamController.php HLS URL'lerini Bunny CDN'den döndür
Song.php getHlsUrl() Bunny URL desteği
config/services.php Bunny config'leri ekle
config/filesystems.php Bunny disk tanımı
config/app.php BunnyServiceProvider ekle
YENİ app/Console/Commands/MigrateToBunnyCommand.php

Mevcut şarkıları batch olarak Bunny'e yükler.

// Kullanım örnekleri:

# Tüm şarkıları yükle (tam gaz)
php artisan muzibu:migrate-to-bunny --all

# 100'lük chunk'lar halinde
php artisan muzibu:migrate-to-bunny --chunk=100

# Belirli ID aralığı
php artisan muzibu:migrate-to-bunny --from=1 --to=1000

# Sadece belirli şarkı
php artisan muzibu:migrate-to-bunny --song=123

# Test modu (yüklemez, sadece listeler)
php artisan muzibu:migrate-to-bunny --all --dry-run

# İlerleme takibi
php artisan muzibu:migrate-to-bunny --all --progress

⚠️ enc.bin ve enc.keyinfo dosyaları OTOMATİK OLARAK HARİÇ TUTULUR!

5 ENV Ayarları

# ===========================================
# BUNNY STORAGE ZONE
# ===========================================

# Ana switch - false iken Bunny devre dışı, local çalışır
BUNNY_STORAGE_ENABLED=false

# Storage Zone API (Bunny Panel → Storage → FTP & API Access)
BUNNY_STORAGE_API_KEY=your-storage-zone-api-key-here
BUNNY_STORAGE_ZONE=muzibu-audio
BUNNY_STORAGE_REGION=de

# CDN URL (Pull Zone bağlandıktan sonra)
BUNNY_CDN_URL=https://muzibu-audio-cdn.b-cdn.net

# ===========================================
# HİBRİT MOD AYARLARI
# ===========================================

# Mod: hybrid (her ikisi) | bunny_only | local_only
BUNNY_MODE=hybrid

# Bunny başarısız olursa local'e düş
BUNNY_FALLBACK_TO_LOCAL=true

# Orijinal MP3'leri de Bunny'e yükle? (opsiyonel, genelde false)
BUNNY_UPLOAD_ORIGINALS=false
Başlangıç Stratejisi
  1. 1. İlk başta BUNNY_STORAGE_ENABLED=false
  2. 2. Kodları deploy et
  3. 3. Migration'ı çalıştır
  4. 4. Test et
  5. 5. Sonra BUNNY_STORAGE_ENABLED=true

6 Batch Migration (280GB Yükleme)

📊 Boyut Tahmini

Şarkı sayısı:~4,000
Per şarkı:~70 MB
Toplam:~280 GB

⏱️ Süre Tahmini

Upload hızı:~100 Mbps
280GB süresi:~6-8 saat
Sunucu etkisi:Minimal

Adım Adım Migration

# 1. Önce dry-run ile kontrol et
php artisan muzibu:migrate-to-bunny --all --dry-run

# 2. Küçük bir test (ilk 10 şarkı)
php artisan muzibu:migrate-to-bunny --from=1 --to=10

# 3. Bunny panel'den dosyaları kontrol et

# 4. Tam migration başlat (screen içinde)
screen -S bunny-migration
php artisan muzibu:migrate-to-bunny --all --progress

# Screen'den çıkmak: Ctrl+A, D
# Screen'e dönmek: screen -r bunny-migration
Güvenlik Garantisi
  • enc.bin YÜKLENMEZ - Otomatik hariç tutulur
  • enc.keyinfo YÜKLENMEZ - Otomatik hariç tutulur
  • Local dosyalar SİLİNMEZ - Sadece kopyalama
  • Hata olursa devam eder - Başarısız dosyaları loglar

7 Test Senaryoları

TEST 1 Bunny'den HLS Çalıyor mu?
# Browser DevTools → Network tab
# Bir şarkı çal ve kontrol et:

✅ master.m3u8    → https://muzibu-audio-cdn.b-cdn.net/hls/123/master.m3u8
✅ playlist.m3u8  → https://muzibu-audio-cdn.b-cdn.net/hls/123/playlist.m3u8
✅ segment-*.ts   → https://muzibu-audio-cdn.b-cdn.net/hls/123/segment-000.ts
✅ encryption key → https://muzibu.com/hls-key/muzibu/songs/123 (SUNUCUDAN!)
TEST 2 Şifreleme Çalışıyor mu?
# Segment'i direkt indirmeye çalış
curl https://muzibu-audio-cdn.b-cdn.net/hls/123/segment-000.ts -o test.ts

# Eğer şifrelenmiş ise:
file test.ts
# Output: "data" (çünkü encrypted, decode edilemez)

# Şifresiz olsaydı:
# Output: "MPEG transport stream data"
TEST 3 Fallback Çalışıyor mu?
# .env'de geçici olarak:
BUNNY_CDN_URL=https://yanlis-url.b-cdn.net

# Şarkı çal → Bunny fail → Local'e düşmeli
# Network tab'da local URL'ler görünmeli

# Sonra düzelt:
BUNNY_CDN_URL=https://muzibu-audio-cdn.b-cdn.net
TEST 4 Yeni Şarkı Dual-Write
# Admin'den yeni şarkı yükle
# HLS convert bekle

# Kontrol et:
# 1. Local'de var mı?
ls storage/tenant1001/app/public/muzibu/hls/[yeni_id]/

# 2. Bunny'de var mı?
# Bunny Panel → Storage → muzibu-audio → hls/[yeni_id]/

# 3. enc.bin Bunny'de YOK olmalı!
TEST 5 Şarkı Silme
# Admin'den bir test şarkısını kalıcı sil (force delete)

# Kontrol et:
# 1. Local'den silindi mi?
ls storage/tenant1001/app/public/muzibu/hls/[silinen_id]/
# "No such file or directory" olmalı

# 2. Bunny'den silindi mi?
# Bunny Panel → Storage → hls/[silinen_id]/ → Olmamalı

✅ Go Live Öncesi Checklist

  • ☐ HLS Bunny'den çalıyor
  • ☐ Key sunucudan geliyor
  • ☐ Şifreleme çalışıyor
  • ☐ MP3 fallback çalışıyor
  • ☐ Yeni şarkı dual-write çalışıyor
  • ☐ Silme her iki yerden çalışıyor
  • ☐ enc.bin Bunny'de YOK
  • ☐ Fallback to local çalışıyor

8 DNS Geçişi (Cloudflare → Bunny)

ÖNEMLİ: Bu adım ayrı bir iş!

Storage Zone entegrasyonu ve DNS geçişi AYRI adımlar. Storage Zone çalıştıktan sonra DNS geçişi yapılır. Bu rapor Storage Zone odaklı. DNS geçişi için mevcut Pull Zone raporuna bak.

1

TTL Düşür

Cloudflare DNS → muzibu.com → TTL: 300 (5 dakika) → 2 saat bekle

2

DNS Değiştir

muzibu.com → CNAME → muzibuweb.b-cdn.net

3

Cloudflare Proxy Kapat

Orange cloud → Grey cloud (DNS only)

4

Bunny SSL Aktif Et

Bunny → Hostnames → muzibu.com → Enable SSL

9 Go Live!

# 1. Son kontroller
php artisan config:clear
php artisan cache:clear
php artisan view:clear

# 2. Bunny'i aktif et
# .env dosyasında:
BUNNY_STORAGE_ENABLED=true

# 3. Config cache
php artisan config:cache

# 4. Test şarkı çal
# Network tab'dan Bunny URL'lerini doğrula
Tebrikler! Bunny Storage Zone aktif!

10 İzleme ve Metrikler

📊 Bunny Statistics

  • Cache HIT oranı: %90+ olmalı
  • Bandwidth kullanımı
  • Response time
  • Error rate: %1'in altında

📋 Log Kontrolü

# Bunny upload hataları
grep "Bunny upload failed" storage/logs/laravel.log

# Player hataları
tail -f storage/logs/player-errors.log

KRİZ YÖNETİMİ — Sorun Çıkarsa Ne Yapılır?

🔥 KRİZ 1: Bunny Tamamen Çöktü

Şarkılar çalmıyor, Bunny CDN erişilemiyor.

# HIZLI ÇÖZÜM: Bunny'i devre dışı bırak (30 saniye)

# 1. .env'de:
BUNNY_STORAGE_ENABLED=false

# 2. Cache temizle
php artisan config:cache
php artisan cache:clear

# Sistem otomatik olarak LOCAL'e döner!
# Kullanıcılar şarkı dinlemeye devam eder.

⚠️ KRİZ 2: Bazı Şarkılar Çalmıyor

Migration eksik kalmış veya upload hatası var.

# 1. Hangi şarkılar sorunlu bul
grep "Bunny upload failed" storage/logs/laravel.log | tail -20

# 2. O şarkıları tekrar yükle
php artisan muzibu:migrate-to-bunny --song=123
php artisan muzibu:migrate-to-bunny --song=456

# 3. Veya fallback aktif ise zaten local'den çalar

⚡ KRİZ 3: Şifreleme Bozuldu

Şarkılar çalıyor ama ses bozuk/gürültü geliyor.

# Bu enc.bin'in yanlışlıkla Bunny'e yüklendiği anlamına gelir!

# 1. Bunny'den enc.bin'leri sil
# Bunny Panel → Storage → hls/ → enc.bin dosyalarını bul ve sil

# 2. Veya Bunny'i kapat, local'e dön
BUNNY_STORAGE_ENABLED=false
php artisan config:cache

# 3. Sorun çözülene kadar local'den devam et

🔄 KRİZ 4: Kod Hatası - Geri Al

Kod değişikliklerinde kritik hata var.

# 1. Backup branch'e dön
git checkout backup-before-bunny-YYYYMMDD

# 2. Deploy et
php artisan config:cache
php artisan cache:clear

# 3. Sistemi eski haline getir
# Bunny'deki dosyalar kalır, zarar vermez

📞 Acil Durum Kontrol Listesi

  1. 1. Panik yapma, fallback var!
  2. 2. BUNNY_STORAGE_ENABLED=false → Anında local'e döner
  3. 3. Logları kontrol et
  4. 4. Gerekirse git backup'a dön
  5. 5. Local dosyalar HER ZAMAN yerinde!

Sıkça Sorulan Sorular

Key sunucudan gelmesi yavaşlatır mı?

Hayır. Key sadece 16 byte ve şarkı başına 1 kez istenir. HLS.js key'i cache'ler, aynı şarkıda tekrar istemez. Toplam gecikme ~50ms, fark edilmez. Üstelik segment'ler Bunny'den geldiği için net kazanç var.

enc.bin'i Bunny'e yüklesek ne olur?

GÜVENLİK RİSKİ! enc.bin şifreleme anahtarı. Bunny public CDN, herkes indirebilir. Anahtar ele geçerse şifreleme anlamsız olur. Bu yüzden enc.bin ve enc.keyinfo ASLA Bunny'e yüklenmemeli.

Batch migration sunucuyu yavaşlatır mı?

Hayır. Sadece dosya okuma + HTTP upload. CPU/RAM kullanımı minimal. Upload bandwidth ayrı, kullanıcı trafiği etkilenmez. Yine de gece yapmak en güvenlisi.

Bunny çökerse ne olur?

Fallback devreye girer. BUNNY_FALLBACK_TO_LOCAL=true olduğu sürece, Bunny erişilemezse otomatik olarak local dosyalardan servis edilir. Local dosyalar HER ZAMAN yerinde!

Aylık maliyet ne kadar artar?

Sadece ~$3/ay. Storage: 280GB × $0.01 = $2.80/ay. Bandwidth zaten Pull Zone'da hesaplanmıştı. Cloudflare'a göre hala $600-800/ay tasarruf!

Maliyet Özeti

$830-1,030
Cloudflare + Argo /ay
$215-265
Bunny Hibrit /ay
$600-800
Aylık Tasarruf 🎉