Tenant Oluşturma Süreci

Multi-Tenant Mimari - Kapsamlı Teknik Analiz

Central + Tenant DB Plesk Entegrasyonu SSL Sertifikası Storage Yönetimi

Basit Anlatım (Herkes İçin)

Tenant Nedir?

Tenant, sistemde bağımsız çalışan bir "kiracı site"dir. Her tenant'ın kendi veritabanı, dosyaları ve ayarları vardır. Örneğin: muzibu.com bir tenant, ixtif.com başka bir tenant'tır.

Ne Oluyor?

Yeni tenant oluşturulduğunda: veritabanı yaratılır, tablolar oluşturulur, dosya klasörleri hazırlanır, domain Plesk'e kaydedilir ve SSL sertifikası yenilenir.

Ne Kadar Sürer?

Tüm işlemler senkron (anında) çalışır. SSL sertifikası yenileme de dahil - wildcard DNS sayesinde *.tuufi.com subdomain'leri için bekleme gerekmez.

Neden Önemli?

Her site izole çalışır - birinin verileri diğerini etkilemez. Plesk entegrasyonu sayesinde domain yönetimi ve SSL otomatiktir.

Genel Akış Şeması

1. Tenant::create() 2. TenantCreated Event 3. CreateDatabase Job 4. MigrateDatabase Job 5. SeedDatabase Job
6. DatabaseMigrated Event 7. Storage + Plesk DB 8. Domain Ekleme 9. Plesk Alias 10. SSL Yenileme

Detaylı Adımlar

1

Tenant::create() - Central DB'ye Kayıt

Kullanıcı yeni tenant oluşturduğunda başlangıç noktası

Tetikleyen Dosya

// Modules/TenantManagement/App/Http/Controllers/ // TenantManagementController.php:41-91 $tenant = Tenant::create([ 'title' => $validated['title'], 'tenancy_db_name' => $dbName, 'is_active' => true, 'theme_id' => $validated['theme_id'], 'data' => $data, ]);

Central DB - tenants Tablosu

AlanAçıklama
idAuto increment
titleTenant adı
tenancy_db_nametenant_[slug]_[6hex] (unique)
is_activeBoolean
theme_idFK → themes
ai_credits_balanceAI kredi bakiyesi
dataJSON ek veriler

DB Name Oluşturma: tenant_[title_slug]_[6_hex_random] formatında unique isim üretilir. Örnek: tenant_kirmizi_a5f2c1

Otomatik Tema Sistemi (t-{id})

Tema seçiminde "Otomatik (t-{id})" seçildiğinde:

  • Tenant ID'ye göre otomatik tema oluşturulur (örn: t-3)
  • Central DB'de themes tablosuna kayıt eklenir
  • resources/views/themes/t-{id}/ klasörü oluşturulur
  • config.json, layouts/app.blade.php, homepage.blade.php dosyaları üretilir
  • Tema simple temasını extend eder (fallback)
Dosya: Modules/TenantManagement/App/Http/Livewire/TenantComponent.php
Metodlar: createAutoTheme(), createAutoThemeFiles()
2

TenantCreated Event → JobPipeline

Tenant kaydedilince otomatik tetiklenen event ve job'lar

// app/Providers/TenancyServiceProvider.php:27-39 Events\TenantCreated::class => [ JobPipeline::make([ Jobs\CreateDatabase::class, // ✅ 1. Veritabanı oluştur Jobs\MigrateDatabase::class, // ✅ 2. Migration'ları çalıştır Jobs\SeedDatabase::class, // ✅ 3. Seeder'ları çalıştır ])->shouldBeQueued(false), // Senkron çalışır ],
CreateDatabase

MySQL'de yeni veritabanı oluşturur: CREATE DATABASE tenant_xxx

MigrateDatabase

php artisan tenants:migrate ile tüm tenant migration'ları çalıştırır

SeedDatabase

php artisan tenants:seed ile başlangıç verilerini ekler

3

Tenant Database Migration'ları

Tenant DB'de oluşturulan tüm tablolar

Base Migrations (database/migrations/tenant/)

  • users - Kullanıcılar
  • cache - Cache tablosu
  • jobs - Queue jobs
  • sessions - Session'lar
  • media - Medya dosyaları
  • activity_log - Activity log
  • module_tenant_settings
  • tenant_usage_logs
  • tags, taggables

Modül Migrations (Modules/*/database/migrations/tenant/)

  • UserManagement: permissions, roles
  • LanguageManagement: tenant_languages
  • MenuManagement: menus, menu_items
  • Page: pages
  • Blog: blog_categories, blogs
  • Portfolio: portfolio_categories, portfolios
  • Shop: 30+ tablo (products, orders vb.)
  • Cart, Payment, Coupon, AI...

Toplam: ~100+ migration dosyası, tüm modüllerin tenant tabloları otomatik oluşturulur.

4

TenantDatabaseSeeder - Başlangıç Verileri

Tenant DB'ye eklenen varsayılan veriler

// database/seeders/TenantDatabaseSeeder.php 1. seedLanguages() // 100 dil kopyala (sadece TR aktif) 2. seedModuleTenants() // Central'dan aktif modülleri al 3. seedPermissions() // Central'dan permission'ları kopyala 4. seedRoles() // root, admin, editor, user rolleri 5. assignPermissionsToRoles() // Rol-permission ilişkileri 6. seedUsers() // 2 varsayılan kullanıcı 7. seedMenu() // Central'dan header menüsünü kopyala 8. seedHomePage() // Anasayfa oluştur 9. seedAIKnowledgeBase() // AI bilgi bankası
Varsayılan Kullanıcılar
  • nurullah@nurullah.net / g0nulcelen → root
  • info@turkbilisim.com.tr / gonu1celen → admin
Varsayılan Roller
  • root - Tüm yetkiler
  • admin - view, create, update (delete hariç)
  • editor - view, create, update
  • user - Sadece view
5

DatabaseMigrated Event → Storage + Plesk DB

Migration tamamlanınca tetiklenen işlemler

// app/Listeners/RegisterTenantDatabaseToPlesk.php Events\DatabaseMigrated::class => [ \App\Listeners\RegisterTenantDatabaseToPlesk::class, ],

Storage Klasörleri Oluşturma

storage/tenant{ID}/
├── framework/
│   ├── cache/
│   ├── sessions/
│   └── views/
├── app/
│   └── public/
└── logs/

Symlink: public/storage/tenant{ID}storage/tenant{ID}/app/public

Plesk Veritabanı Kaydı

// Plesk data_bases tablosuna INSERT 1. Domain ID bul (tuufi.com → 1) 2. DB Server ID bul (localhost → 1) 3. INSERT INTO data_bases: - name: tenant_xxx - type: mysql - dom_id: 1 - db_server_id: 1

3 deneme, 2 saniye bekleme (retry logic)

6

Domain Ekleme → Central DB

Tenant'a domain eklendiğinde tetiklenen işlemler

// Central DB - domains tablosu $domain = Domain::create([ 'domain' => 'yenisite.com', 'tenant_id' => $tenant->id, ]);
AlanAçıklama
idAuto increment
tenant_idFK → tenants.id (cascade delete)
domainDomain adı (unique)
is_primaryPrimary domain flag
7

DomainCreated Event → Otomatik WWW + Plesk Alias

Domain oluşturulunca tetiklenen listener'lar

// app/Providers/TenancyServiceProvider.php:56-59 Events\DomainCreated::class => [ \App\Listeners\CreateTenantDomains::class, // ✅ Otomatik www.domain \App\Listeners\RegisterDomainAliasInPlesk::class, // ✅ Plesk alias ],
CreateTenantDomains

Dosya: app/Listeners/CreateTenantDomains.php

  • yenisite.com eklenince
  • • Otomatik www.yenisite.com eklenir
  • • Central tenant'lara ekleme yapmaz
RegisterDomainAliasInPlesk

Dosya: app/Listeners/RegisterDomainAliasInPlesk.php

sudo /usr/sbin/plesk bin domalias \ --create yenisite.com \ -domain tuufi.com \ -web true -mail true -dns true \ -seo-redirect false

Önemli: Tüm domain'ler tuufi.com'a alias olarak bağlanır. Plesk alias oluşturulunca www subdomain'i otomatik eklenir.

8

SSL Sertifikası Yenileme (Let's Encrypt)

Yeni domain eklenince SSL sertifikası otomatik ve senkron olarak yenilenir

// app/Jobs/ReissueLetsEncryptCertificate.php // Senkron çalışır - dispatchSync() ile ReissueLetsEncryptCertificate::dispatchSync(); // Plesk Let's Encrypt komutu sudo /usr/sbin/plesk bin extension --exec letsencrypt cli.php \ -d tuufi.com -d www.tuufi.com \ -d yenisite.com -d www.yenisite.com \ -d eskisite.com -d www.eskisite.com \ ... (tüm domain'ler) \ -m ssl@tuufi.com
Job Özellikleri
  • $tries = 2 - 2 deneme
  • $timeout = 120 - 2 dakika timeout
  • $backoff = 30 - 30 sn retry bekleme
  • Senkron - Tenant kayıt bitmeden SSL hazır
Çalışma Mantığı
  • 1. Central DB'den tüm domain'leri al
  • 2. Her domain için -d parametresi ekle
  • 3. www. subdomain'leri de ekle
  • 4. Plesk Let's Encrypt CLI çalıştır

Wildcard DNS: *.tuufi.com wildcard DNS kaydı sayesinde subdomain'ler (örn: panjur.tuufi.com) için DNS propagation beklemeye gerek kalmaz.

Rate Limit: Let's Encrypt rate limit'e takılırsa warning log yazılır ve daha sonra tekrar denenir.

9

TenancyInitialized Event - Runtime Konfigürasyonu

Tenant context'e geçildiğinde yapılan ayarlar

// app/Providers/TenancyServiceProvider.php:88-146 Events\TenancyInitialized::class => [ // 1. Log yapılandırması config(['logging.channels.tenant.path' => storage_path('logs/tenant.log')]); // 2. Session lifetime (tenant setting'den) config(['session.lifetime' => setting('auth_session_lifetime', 525600)]); // 3. Session prefix (Redis için) config(['database.redis.session.options.prefix' => "tenant_{$tenantId}_session_"]); // 4. Cookie domain (.domain.com formatında) config(['session.domain' => '.yenisite.com']); ],

Tenant/Domain Silme İşlemleri

Tenant Silindiğinde

Events\TenantDeleted::class => [ SafeDeleteDatabase::class, // DB sil UnregisterDatabaseFromPlesk::class, // Plesk'ten kaldır ],
  • Veritabanı güvenli silinir
  • Plesk data_bases kaydı silinir
  • Domain'ler cascade delete ile silinir

Domain Silindiğinde

Events\DeletingDomain::class => [ UnregisterDomainAliasFromPlesk::dispatchSync(), ],
  • Plesk alias silinir (domalias --delete)
  • Senkron çalışır (domain silinmeden önce)
  • SSL'den otomatik çıkar (bir sonraki yenilemede)

Kritik Dosyalar Özet

Dosya Rol Açıklama
app/Models/Tenant.php Model Tenant modeli, relationships, AI helpers
app/Models/Domain.php Model Domain modeli (stancl/tenancy)
app/Providers/TenancyServiceProvider.php Provider Tüm event → listener mapping'leri
app/Listeners/RegisterTenantDatabaseToPlesk.php Listener Storage oluşturma + Plesk DB kaydı
app/Listeners/RegisterDomainAliasInPlesk.php Listener Plesk domain alias oluşturma
app/Listeners/CreateTenantDomains.php Listener Otomatik www.domain ekleme
app/Jobs/ReissueLetsEncryptCertificate.php Job SSL sertifikası yenileme (queue)
app/Jobs/UnregisterDomainAliasFromPlesk.php Job Plesk domain alias silme
app/Jobs/UnregisterDatabaseFromPlesk.php Job Plesk DB kaydı silme
database/seeders/TenantDatabaseSeeder.php Seeder Tenant başlangıç verileri
Modules/TenantManagement/.../Controller.php Controller CRUD API endpoints

Plesk Komutları Özet

Oluşturma Komutları

Domain Alias Oluştur:

sudo /usr/sbin/plesk bin domalias --create {domain} -domain tuufi.com -web true -mail true -dns true -seo-redirect false

DB Kaydı (SQL):

sudo /usr/sbin/plesk db "INSERT INTO data_bases (name, type, dom_id, db_server_id) VALUES ('{dbName}', 'mysql', {domainId}, {serverId})"

SSL Yenile:

sudo /usr/sbin/plesk bin extension --exec letsencrypt cli.php -d domain1 -d domain2 ... -m ssl@tuufi.com

Silme Komutları

Domain Alias Sil:

sudo /usr/sbin/plesk bin domalias --delete {domain}

DB Kaydı Sil (SQL):

sudo /usr/sbin/plesk db "DELETE FROM data_bases WHERE name = '{dbName}'"

Alias Kontrol:

sudo /usr/sbin/plesk bin domalias --info {domain}

Özet: Tüm Oluşturulan Kaynaklar

Central DB

  • • tenants tablosu
  • • domains tablosu
  • • module_tenants

Tenant DB

  • • MySQL database
  • • 100+ tablo
  • • Başlangıç verileri

Dosya Sistemi

  • • storage/tenant{ID}/
  • • public symlink
  • • logs klasörü

Plesk

  • • data_bases kaydı
  • • Domain alias
  • • SSL sertifikası

Yeni Tenant Oluşturma TODO Checklist

Otomatik Yapılanlar (Sistem)

Central DB - tenants tablosuna kayıt

id, title, tenancy_db_name, theme_id, is_active...

MySQL veritabanı oluştur

tenant_[slug]_[6hex] formatında unique isim

Tenant migration'ları çalıştır

php artisan tenants:migrate (100+ tablo)

Seeder çalıştır (başlangıç verileri)

Diller, roller, kullanıcılar, menü, anasayfa

Storage klasörleri oluştur

storage/tenant{ID}/ (cache, sessions, views, logs, app/public)

Public symlink oluştur

public/storage/tenant{ID} → storage/tenant{ID}/app/public

Plesk veritabanı kaydı

INSERT INTO data_bases (tuufi.com domain'ine bağlı)

Otomatik tema oluştur (opsiyonel)

"Otomatik (t-{id})" seçildiyse: t-{id} teması + dosyaları oluşturulur

Domain Eklendiğinde (Otomatik)

Central DB - domains tablosuna kayıt

domain (unique), tenant_id (FK)

Otomatik www. subdomain ekle

yenisite.com → www.yenisite.com otomatik eklenir

Plesk domain alias oluştur

plesk bin domalias --create (web, mail, dns enabled)

SSL sertifikası yenile (Let's Encrypt)

Senkron çalışır - tenant kaydı bitmeden SSL hazır olur

Manuel Kontroller (Opsiyonel)

DNS kayıtları kontrol et

Domain'in tuufi.com sunucusuna yönlendirildiğinden emin ol

SSL sertifikası kontrolü

https://yenisite.com açılıyor mu? Sertifika geçerli mi?

Tenant CSS oluştur (gerekirse)

npm run prod veya npm run css:tenant{ID}

Admin panele giriş test et

nurullah@nurullah.net / g0nulcelen ile giriş yap

Hızlı Kontrol Komutları

# Plesk alias kontrol:

sudo /usr/sbin/plesk bin domalias --info yenisite.com

# SSL sertifika kontrol:

curl -vI https://yenisite.com 2>&1 | grep -i "SSL\|expire"

# Tenant DB kontrol:

mysql -e "SHOW DATABASES LIKE 'tenant_%'"

# Storage symlink kontrol:

ls -la public/storage/ | grep tenant

Oluşturma Akışı Özeti

1. Tenant::create() 2. Auto Theme (t-{id}) 3. CreateDatabase 4. MigrateDatabase 5. SeedDatabase 6. Storage + Plesk DB 7. Domain Ekle 8. Plesk Alias 9. SSL (Senkron) ✓ HAZIR

Olası Problemler ve Hızlı Çözümler v2 - 20 Ocak 2026

SSL Sertifika Hatası (ERR_CERT_COMMON_NAME_INVALID)

Tarayıcıda "Bağlantınız gizli değil" hatası alınıyor.

Neden: Nginx/Apache config'e domain eklenmemiş veya SSL sertifikası yenilenmemiş.

Çözüm Adımları:

# 1. Nginx'te domain var mı kontrol et:
grep "domain.com" /etc/nginx/plesk.conf.d/vhosts/tuufi.com.conf
# 2. Yoksa ekle (son domain'den sonra):
sudo sed -i '/server_name www.soneklenen.com;/a\\tserver_name domain.com;\\n\\tserver_name www.domain.com;' /etc/nginx/plesk.conf.d/vhosts/tuufi.com.conf
# 3. Apache'ye de ekle:
sudo sed -i '/ServerAlias "www.soneklenen.com"/a\\t\\tServerAlias "domain.com"\\n\\t\\tServerAlias "www.domain.com"' /var/www/vhosts/system/tuufi.com/conf/httpd.conf
# 4. Servisleri reload et:
sudo systemctl reload nginx && sudo systemctl reload httpd
# 5. SSL sertifikasını yenile:
sudo certbot certonly --webroot -w /var/www/vhosts/default/htdocs -d tuufi.com -d www.tuufi.com -d domain.com -d www.domain.com --cert-name tuufi-all --expand
# 6. Sertifikayı Plesk'e kopyala:
sudo bash -c 'cat /etc/letsencrypt/live/tuufi-all/fullchain.pem /etc/letsencrypt/live/tuufi-all/privkey.pem > /usr/local/psa/var/certificates/scffm1s7qbch4jnfprJ4Ox' && sudo chmod 600 /usr/local/psa/var/certificates/scffm1s7qbch4jnfprJ4Ox && sudo systemctl reload nginx

HTTP 500 Internal Server Error

Site açılmıyor, 500 hatası alınıyor.

Olası Nedenler: Tenant DB tabloları eksik, tema dosyaları yok, veya config hatası.

Kontrol ve Çözüm:

# 1. Laravel log'ları kontrol et:
tail -100 storage/logs/laravel-*.log | grep -i error
# 2. Tenant log'ları kontrol et:
tail -100 storage/tenant{ID}/logs/tenant-*.log
# 3. Tenant DB'de tablo sayısını kontrol et:
php artisan tinker --execute="Tenant::find({ID})->run(fn() => DB::select('SHOW TABLES'));"
# 4. Migration eksikse çalıştır:
php artisan tenants:migrate --tenants={ID} --force
# 5. Cache temizle:
php artisan cache:clear && php artisan view:clear && php artisan config:clear

404 Not Found

Domain açılıyor ama 404 hatası alınıyor.

Neden: Apache ServerAlias eksik veya domain tenant'a bağlı değil.

Kontrol ve Çözüm:

# 1. Apache'de domain var mı:
grep "domain.com" /var/www/vhosts/system/tuufi.com/conf/httpd.conf
# 2. Domain tenant'a bağlı mı:
php artisan tinker --execute="Domain::where('domain', 'domain.com')->first();"
# 3. Apache'ye ekle:
sudo sed -i '/UseCanonicalName Off/i\\t\\tServerAlias "domain.com"\\n\\t\\tServerAlias "www.domain.com"' /var/www/vhosts/system/tuufi.com/conf/httpd.conf
# 4. Apache reload:
sudo apachectl configtest && sudo systemctl reload httpd

Cloudflare Domain SSL Hatası

Certbot çalışırken "unauthorized" hatası alınıyor.

Neden: Cloudflare proxy aktifken webroot doğrulaması çalışmaz.

Çözüm:

Cloudflare arkasındaki domain'ler SSL listesinden çıkarılmalı:

# app/Jobs/RenewSSLCertificate.php
# isRedirectDomain() metoduna ekle:
$excludedDomains = ['cloudflare-domain.com'];

Cloudflare kendi SSL sertifikasını sağlar, sunucu tarafında SSL gerekmez.

Domain/SSL İşlemleri İçin Kritik Dosyalar

app/Jobs/RegisterDomainInWebServer.php

Nginx/Apache config'e domain ekler (dinamik, son domain'den sonra)

app/Jobs/RenewSSLCertificate.php

Let's Encrypt ile SSL sertifikası yeniler, Cloudflare domain'leri filtreler

/etc/nginx/plesk.conf.d/vhosts/tuufi.com.conf

Nginx vhost config - server_name satırları

/var/www/vhosts/system/tuufi.com/conf/httpd.conf

Apache vhost config - ServerAlias satırları

Hızlı Tanı Komutları

# HTTP durum kodu kontrolü:

curl -sI https://domain.com | head -1

# SSL sertifika domain kontrolü:

echo | openssl s_client -servername domain.com -connect domain.com:443 2>/dev/null | openssl x509 -noout -text | grep DNS:

# System log kontrolü:

tail -50 storage/logs/system-*.log | grep domain.com

# Nginx config syntax test:

sudo nginx -t