Otomatik Güncelleme: WordPress Eklenti Dağıtım Sistemi Nasıl Kurulur
Bir sabah uyandığınızda 47 farklı WordPress sitesinin eklentilerini tek tek güncellemek zorunda olduğunuzu düşünün. Her birine giriş yapıyorsunuz, güncelleme düğmesine basıyorsunuz, bir şeylerin bozulup bozulmadığını kontrol ediyorsunuz. Bu döngü saatler alıyor ve dürüst olmak gerekirse, bu işi yaparken kendinizi bir otomasyon mühendisi değil, bir fare tıklama robotu gibi hissediyorsunuz. İşte bu yazıda, WordPress eklenti dağıtım sistemini nasıl otomatize edebileceğinizi, kendi özel güncelleme sunucunuzu nasıl kurabileceğinizi ve bütün bu süreci nasıl yönetilebilir hale getirebileceğinizi ele alacağız.
WordPress Güncelleme Mekanizması Nasıl Çalışır?
WordPress’in yerleşik güncelleme sistemi aslında oldukça zarif bir tasarıma sahip. Her eklenti, WordPress.org’un API’sine belirli aralıklarla istek atıyor. Bu istek şu adrese gidiyor: https://api.wordpress.org/plugins/update-check/1.1/. WordPress bu endpoint’e mevcut eklenti versiyonlarını gönderiyor, karşılığında da güncel versiyonlar ve indirme URL’leri alıyor.
Kendi dağıtım sisteminizi kurmak istediğinizde, bu mekanizmayı kendi sunucunuza yönlendirmeniz gerekiyor. update_plugins filtresini kullanarak WordPress’e “sen şu adrese git, oradan güncelleme bilgilerini al” diyebiliyorsunuz.
Şimdi bu sistemin anatomisini parçalara ayıralım.
Özel Güncelleme Sunucusu Kurulumu
Güncelleme sunucunuzun iki temel bileşene ihtiyacı var: birincisi eklenti metadata’sını döndüren bir API endpoint’i, ikincisi de eklenti paketlerini barındıran bir depo.
Ben bu amaçla genellikle basit bir PHP uygulaması kullanıyorum. Nginx arkasında koşan, MySQL veritabanına bağlı, eklenti bilgilerini JSON olarak döndüren minimal bir yapı. Gelin bunu sıfırdan kuralım.
Sunucu Tarafı: API Endpoint
İlk olarak veritabanı şemanızı oluşturun:
mysql -u root -p wordpress_updates << 'EOF'
CREATE TABLE IF NOT EXISTS plugin_releases (
id INT AUTO_INCREMENT PRIMARY KEY,
plugin_slug VARCHAR(255) NOT NULL,
version VARCHAR(50) NOT NULL,
download_url VARCHAR(1024) NOT NULL,
tested_up_to VARCHAR(20),
requires_php VARCHAR(20),
changelog TEXT,
release_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_stable TINYINT(1) DEFAULT 1,
UNIQUE KEY unique_release (plugin_slug, version)
);
EOF
Ardından API endpoint’inizin ana dosyasını oluşturun. Bu endpoint, WordPress’in beklediği formatta yanıt verecek:
cat > /var/www/update-server/api.php << 'PHPEOF'
<?php
header('Content-Type: application/json');
$pdo = new PDO(
'mysql:host=localhost;dbname=wordpress_updates;charset=utf8',
'db_user',
'db_pass'
);
$action = $_GET['action'] ?? '';
$slug = $_GET['slug'] ?? '';
if ($action === 'plugin_information' && $slug) {
$stmt = $pdo->prepare(
'SELECT * FROM plugin_releases
WHERE plugin_slug = ? AND is_stable = 1
ORDER BY release_date DESC LIMIT 1'
);
$stmt->execute([$slug]);
$plugin = $stmt->fetch(PDO::FETCH_ASSOC);
if ($plugin) {
echo json_encode([
'name' => $slug,
'slug' => $slug,
'version' => $plugin['version'],
'download_link' => $plugin['download_url'],
'tested' => $plugin['tested_up_to'],
'requires_php' => $plugin['requires_php'],
'sections' => [
'changelog' => $plugin['changelog']
]
]);
} else {
echo json_encode(['error' => 'Plugin not found']);
}
}
PHPEOF
İstemci Tarafı: WordPress Eklenti Kodu
Şimdi eklentinizin içinde, güncelleme sistemine kendinizi dahil etmeniz gerekiyor. Bu kod, eklentinizin ana PHP dosyasına gidecek:
cat > /path/to/your-plugin/includes/class-updater.php << 'PHPEOF'
<?php
class YourPlugin_Updater {
private $update_server_url;
private $plugin_slug;
private $plugin_file;
private $current_version;
public function __construct($server_url, $plugin_slug, $plugin_file, $version) {
$this->update_server_url = $server_url;
$this->plugin_slug = $plugin_slug;
$this->plugin_file = $plugin_file;
$this->current_version = $version;
add_filter('pre_set_site_transient_update_plugins', [$this, 'check_update']);
add_filter('plugins_api', [$this, 'plugin_info'], 20, 3);
}
public function check_update($transient) {
if (empty($transient->checked)) {
return $transient;
}
$remote = wp_remote_get(
$this->update_server_url . '?action=plugin_information&slug=' . $this->plugin_slug,
['timeout' => 15, 'headers' => ['Accept' => 'application/json']]
);
if (is_wp_error($remote) || wp_remote_retrieve_response_code($remote) !== 200) {
return $transient;
}
$remote_data = json_decode(wp_remote_retrieve_body($remote));
if ($remote_data && version_compare($this->current_version, $remote_data->version, '<')) {
$transient->response[$this->plugin_file] = (object)[
'slug' => $this->plugin_slug,
'plugin' => $this->plugin_file,
'new_version' => $remote_data->version,
'url' => $this->update_server_url,
'package' => $remote_data->download_link,
];
}
return $transient;
}
}
PHPEOF
Sürüm Yönetimi ve Paket Hazırlama
En çok gözden kaçan nokta şu: güncelleme paketi hazırlamak, sadece zip dosyası oluşturmaktan ibaret değil. Paketi oluşturmadan önce bazı kontrollerin yapılması şart.
Benim için işe yarayan yaklaşım, CI/CD pipeline’ına entegre bir derleme scripti kullanmak. Bu script hem versiyonu artırıyor, hem de paketi imzalayarak sunucuya yükliyor:
#!/bin/bash
# build-and-deploy.sh
set -euo pipefail
PLUGIN_SLUG="my-awesome-plugin"
PLUGIN_DIR="/home/deploy/plugins/${PLUGIN_SLUG}"
DEPLOY_SERVER="updates.example.com"
DEPLOY_PATH="/var/www/update-server/packages"
DB_CONN="mysql -u deploy_user -p'secret' wordpress_updates"
# Versiyonu plugin ana dosyasından oku
CURRENT_VERSION=$(grep "Version:" "${PLUGIN_DIR}/${PLUGIN_SLUG}.php" | awk '{print $2}')
echo "Mevcut versiyon: ${CURRENT_VERSION}"
# Gereksiz dosyaları hariç tut
EXCLUDE_LIST=(
".git"
".gitignore"
"tests/"
"node_modules/"
"*.log"
"composer.json"
"package.json"
)
EXCLUDE_ARGS=""
for item in "${EXCLUDE_LIST[@]}"; do
EXCLUDE_ARGS+="--exclude=${item} "
done
# Zip paketi oluştur
cd /tmp
rm -rf "${PLUGIN_SLUG}"
cp -r "${PLUGIN_DIR}" "${PLUGIN_SLUG}"
zip -r "${PLUGIN_SLUG}-${CURRENT_VERSION}.zip"
"${PLUGIN_SLUG}/"
${EXCLUDE_ARGS}
echo "Paket oluşturuldu: ${PLUGIN_SLUG}-${CURRENT_VERSION}.zip"
# Sunucuya yükle
scp "${PLUGIN_SLUG}-${CURRENT_VERSION}.zip"
"deploy@${DEPLOY_SERVER}:${DEPLOY_PATH}/"
# Veritabanını güncelle
DOWNLOAD_URL="https://${DEPLOY_SERVER}/packages/${PLUGIN_SLUG}-${CURRENT_VERSION}.zip"
ssh "deploy@${DEPLOY_SERVER}" "${DB_CONN} -e "
INSERT INTO plugin_releases
(plugin_slug, version, download_url, tested_up_to, requires_php)
VALUES
('${PLUGIN_SLUG}', '${CURRENT_VERSION}', '${DOWNLOAD_URL}', '6.4', '8.0')
ON DUPLICATE KEY UPDATE
download_url = VALUES(download_url),
release_date = NOW();
""
echo "Deployment tamamlandi: v${CURRENT_VERSION}"
Güvenlik: İmzalama ve Doğrulama
Burada çok ciddi bir konuya gelelim. Otomatik güncelleme sistemi kurduğunuzda, aslında müşterilerinizin sunucularına kod dağıtma yetkisi ediniyorsunuz. Bu yetkinin kötüye kullanılması veya ele geçirilmesi felaket anlamına gelir. Paket imzalama bu yüzden opsiyonel değil, zorunlu.
#!/bin/bash
# sign-package.sh
PACKAGE_FILE=$1
PRIVATE_KEY="/etc/deploy-keys/private.pem"
SIGNATURE_FILE="${PACKAGE_FILE}.sig"
# Paketi imzala
openssl dgst -sha256 -sign "${PRIVATE_KEY}"
-out "${SIGNATURE_FILE}"
"${PACKAGE_FILE}"
# Base64 encode et (API yanıtına eklemek için)
SIGNATURE_B64=$(base64 -w 0 "${SIGNATURE_FILE}")
echo "İmza: ${SIGNATURE_B64}"
# Checksum oluştur
SHA256=$(sha256sum "${PACKAGE_FILE}" | awk '{print $1}')
echo "SHA256: ${SHA256}"
WordPress eklentisi tarafında indirme tamamlandıktan sonra imzayı doğrulamanız gerekiyor. upgrader_process_complete hook’unu kullanarak bunu yapabilirsiniz, ancak gerçek doğrulama upgrader_pre_install hook’unda olmalı:
cat >> /path/to/your-plugin/includes/class-updater.php << 'PHPEOF'
public function verify_package_signature($source, $remote_source, $upgrader) {
$public_key_pem = file_get_contents(
plugin_dir_path($this->plugin_file) . 'keys/public.pem'
);
$package_url = $upgrader->skin->options['url'] ?? '';
$signature_url = $package_url . '?action=signature&slug=' . $this->plugin_slug;
$sig_response = wp_remote_get($signature_url);
if (is_wp_error($sig_response)) {
return new WP_Error('signature_fetch_failed', 'İmza alınamadı');
}
$signature = base64_decode(wp_remote_retrieve_body($sig_response));
$zip_contents = file_get_contents($source);
$public_key = openssl_pkey_get_public($public_key_pem);
$verified = openssl_verify($zip_contents, $signature, $public_key, OPENSSL_ALGO_SHA256);
if ($verified !== 1) {
return new WP_Error('invalid_signature', 'Paket imzası geçersiz! Güncelleme iptal edildi.');
}
return $source;
}
PHPEOF
Çok Siteli Ortamlarda Dağıtım Stratejisi
Onlarca veya yüzlerce site yönetiyorsanız, hepsine aynı anda güncelleme göndermek akıllıca değil. Canary deployment yaklaşımı burada çok işe yarıyor. Önce küçük bir gruba dağıtıyorsunuz, sorun yoksa herkese açıyorsunuz.
#!/bin/bash
# canary-deploy.sh
PLUGIN_SLUG=$1
NEW_VERSION=$2
CANARY_PERCENTAGE=${3:-10}
TOTAL_SITES=$(mysql -u root -p'pass' wordpress_updates -se
"SELECT COUNT(*) FROM registered_sites WHERE active = 1;")
CANARY_COUNT=$(echo "scale=0; ${TOTAL_SITES} * ${CANARY_PERCENTAGE} / 100" | bc)
echo "Toplam site: ${TOTAL_SITES}"
echo "Canary grubuna dahil edilecek: ${CANARY_COUNT} site"
# Canary grubuna ver
mysql -u root -p'pass' wordpress_updates << SQL
UPDATE registered_sites
SET allowed_version = '${NEW_VERSION}'
WHERE active = 1
AND deployment_group = 'canary'
LIMIT ${CANARY_COUNT};
SQL
echo "Canary deployment başlatıldı. 24 saat bekleyip logları kontrol edin."
echo "Sorun yoksa tam deployment için: ./promote-to-stable.sh ${PLUGIN_SLUG} ${NEW_VERSION}"
API endpoint’inizi de bu mantığa göre güncelleyin. Site, güncelleme kontrolü yaptığında, o sitenin hangi gruba atandığını kontrol edin ve buna göre versiyon döndürün:
cat >> /var/www/update-server/api.php << 'PHPEOF'
function get_allowed_version_for_site($pdo, $slug, $site_url) {
$stmt = $pdo->prepare(
'SELECT rs.allowed_version, pr.download_url
FROM registered_sites rs
JOIN plugin_releases pr
ON pr.plugin_slug = ? AND pr.version = rs.allowed_version
WHERE rs.site_url = ?'
);
$stmt->execute([$slug, $site_url]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
PHPEOF
Rollback Mekanizması
Her deployment sistemi, geri alma mekanizması olmadan eksik. WordPress kendi backup mekanizmasına sahip değil, ama bunu kendiniz ekleyebilirsiniz:
#!/bin/bash
# rollback-plugin.sh
# Kullanim: ./rollback-plugin.sh site_url plugin_slug previous_version
SITE_URL=$1
PLUGIN_SLUG=$2
ROLLBACK_VERSION=$3
WP_CLI_PATH="/usr/local/bin/wp"
SITES_BASE="/var/www"
# Site dizinini bul
SITE_DIR=$(grep -r "${SITE_URL}" /etc/nginx/sites-enabled/ |
grep -oP 'roots+K[^;]+' | head -1)
if [ -z "${SITE_DIR}" ]; then
echo "Hata: Site dizini bulunamadi - ${SITE_URL}"
exit 1
fi
# Mevcut versiyonu yedekle
BACKUP_DIR="/var/backups/plugin-rollbacks/${PLUGIN_SLUG}"
mkdir -p "${BACKUP_DIR}"
CURRENT_VERSION=$(${WP_CLI_PATH} plugin get "${PLUGIN_SLUG}"
--field=version
--path="${SITE_DIR}"
--allow-root 2>/dev/null)
if [ -n "${CURRENT_VERSION}" ]; then
tar czf "${BACKUP_DIR}/${PLUGIN_SLUG}-${CURRENT_VERSION}-$(date +%Y%m%d%H%M%S).tar.gz"
"${SITE_DIR}/wp-content/plugins/${PLUGIN_SLUG}/"
echo "Mevcut versiyon yedeklendi: v${CURRENT_VERSION}"
fi
# Önceki versiyonun paketini çek
ROLLBACK_URL=$(mysql -u root -p'pass' wordpress_updates -se
"SELECT download_url FROM plugin_releases
WHERE plugin_slug='${PLUGIN_SLUG}' AND version='${ROLLBACK_VERSION}';")
if [ -z "${ROLLBACK_URL}" ]; then
echo "Hata: Rollback versiyonu bulunamadi - v${ROLLBACK_VERSION}"
exit 1
fi
# WP-CLI ile yükle
${WP_CLI_PATH} plugin install "${ROLLBACK_URL}"
--force
--path="${SITE_DIR}"
--allow-root
echo "Rollback tamamlandi: ${SITE_URL} -> v${ROLLBACK_VERSION}"
# Cache temizle
${WP_CLI_PATH} cache flush --path="${SITE_DIR}" --allow-root
${WP_CLI_PATH} rewrite flush --path="${SITE_DIR}" --allow-root
İzleme ve Raporlama
Dağıtım sisteminizin ne kadar etkin çalıştığını görmek için bir izleme katmanına ihtiyacınız var. Hangi sitelerin hangi versiyonu çalıştırdığını, başarısız güncellemelerin nerede olduğunu takip etmek kritik:
#!/bin/bash
# update-status-report.sh
echo "=== Eklenti Dagitim Durum Raporu ==="
echo "Tarih: $(date '+%Y-%m-%d %H:%M')"
echo ""
mysql -u root -p'pass' wordpress_updates << 'SQL'
SELECT
rs.site_url,
rs.current_version,
rs.allowed_version,
CASE
WHEN rs.current_version = rs.allowed_version THEN 'GUNCEL'
WHEN rs.current_version < rs.allowed_version THEN 'BEKLIYOR'
ELSE 'BILINMIYOR'
END as durum,
rs.last_check_date
FROM registered_sites rs
WHERE rs.active = 1
ORDER BY durum DESC, rs.last_check_date ASC;
SQL
Bu script’i bir cron job’a bağlayın ve çıktısını bir Slack kanalına veya e-posta adresinize gönderin. Ben bunu her sabah 08:00’de çalıştırıp sonuçları bir Slack webhook’una iletiyorum. Günün başında birkaç mesaj, o gün neyle uğraşacağınızı önceden söylüyor.
Gerçek Dünyada Karşılaşılan Sorunlar
Bu sistemi kurumsal bir müşteri için devreye aldığımda karşılaştığım birkaç sorunu paylaşmak istiyorum.
Proxy arkasındaki siteler: Bazı müşteriler kurumsal proxy kullanıyor. WordPress’in wp_remote_get fonksiyonu doğrudan bağlantı yapmaya çalışıyor ve başarısız oluyor. wp-config.php dosyasına proxy ayarlarını eklemek gerekiyor:
# wp-config.php'ye eklenecek satırlar
define('WP_PROXY_HOST', '10.0.0.1');
define('WP_PROXY_PORT', '8080');
define('WP_PROXY_BYPASS_HOSTS', 'updates.example.com');
Transient cacheleme: WordPress güncelleme kontrollerini transient ile cache’liyor. Acil bir güncelleme dağıtmanız gerektiğinde, tüm sitelerdeki cache’i temizlemeniz gerekebilir. WP-CLI bunu toplu yapmanızı sağlıyor:
# Tum sitelerde update transient'i temizle
for site_dir in /var/www/*/; do
if [ -f "${site_dir}wp-config.php" ]; then
wp transient delete update_plugins
--path="${site_dir}"
--allow-root 2>/dev/null &&
echo "Temizlendi: ${site_dir}"
fi
done
Lisans tabanlı erişim kontrolü: Premium eklentiler için güncelleme erişimini lisansa bağlamanız gerekiyor. API endpoint’inize bir lisans doğrulama katmanı ekleyin. Her istek, site URL’si ve lisans anahtarıyla birlikte gelsin, siz de bunu veritabanınızda doğrulayın. Lisansı geçersiz veya süresi dolmuş sitelere güncelleme paketini vermeyin.
Sonuç
WordPress eklenti dağıtım sistemini otomatize etmek, başlangıçta karmaşık görünse de birkaç gün içinde kurulabilen ve uzun vadede inanılmaz miktarda zaman kazandıran bir altyapı. Kritik noktalara bir kez daha değinelim:
- Güvenlik ilk sırada: İmzalama ve doğrulama olmadan bu sistemi production’a almayın. Bir güncelleme sunucusu hacklenirse, tüm müşterilerinize zararlı kod dağıtma kapısı açılır.
- Canary deployment: Yüzlerce siteyi yönetiyorsanız, kademeli dağıtım hayat kurtarır. Bir hatayı tüm sitelerde değil, beş sitede tespit etmek çok daha az can sıkıcıdır.
- Rollback her zaman hazır olsun: Dağıtmadan önce bir önceki versiyona dönme planınızın hazır olduğundan emin olun.
- İzleme eksik bırakılmasın: Kaç sitenin güncellendiğini, kaçının hala eski versiyonda kaldığını görmeden sistemin sağlıklı çalışıp çalışmadığını bilemezsiniz.
Bu altyapıyı bir kez kurduğunuzda, o 47 sitelik güncelleme döngüsü sabah kahvenizi demlediğiniz süre içinde kendi kendine tamamlanıyor. İşte bu noktada kendinizi gerçek anlamda bir otomasyon mühendisi gibi hissediyorsunuz.
