#Runbook — yedekleme ve geri yükleme

Denenmemiş yedek, yedek değildir. Bu dosyadaki geri yükleme prosedürü 14 Ağustos 2026'da baştan sona çalıştırıldı: veritabanı tamamen düşürüldü ve yedekten geri getirildi. Sonuç bölüm 4'te.

#1. Yedek nasıl alınıyor

Uygulama her gece 02:00 UTC'de pg_dump --format=custom çalıştırır ve çıktıyı /data/backups altına yazar (notusship-YYYYAAGG-SSDDss.dump). Varsayılan saklama süresi 14 gün; eskiler otomatik silinir.

İş Hangfire ile zamanlanır. Süreç yedek sırasında yeniden başlarsa iş kaybolmaz.

Elle yedek almak için:

docker compose exec app dotnet NotusShip.Api.dll backup-now

Zamanlanmış işle aynı kodu çalıştırır. Yükseltmeden önce her zaman bunu çalıştırın.

#Yedeklerin sunucu dışına çıkarılması

V0 yedeği yerel bir volume'a yazar. Sunucunun tamamı kaybolursa yedek de kaybolur; bu yüzden notusship-backups volume'unu dışarı senkronlamak operatörün işidir:

# örnek: gecelik dış kopyalama (cron)
docker run --rm -v notusship_notusship-backups:/backups:ro -v /mnt/yedek:/hedef \
  alpine sh -c 'cp -a /backups/. /hedef/'

S3 uyumlu depoya doğrudan yükleme V1'de gelecek — docs/02-fonksiyonlar.md S3 depolamayı V1 kapsamına koyuyor ve tek bir yedek özelliği için SDK eklemek V0'ı ağırlaştırırdı.

#Dosya ekleri ayrı yedeklenir

pg_dump yalnızca veritabanını alır. Issue ekleri notusship-files volume'undadır ve ayrıca kopyalanmalıdır. Veritabanını geri yükleyip dosyaları geri yüklememek, ekleri kırık bağlantıya çevirir.


#2. Geri yükleme prosedürü

Süre: yaklaşık 2 dakika. Uygulama bu süre boyunca kapalıdır.

#Adım 0 — hangi yedeği kullanacağınızı seçin

docker compose exec db ls -lh /backups

#Adım 1 — uygulamayı durdurun

docker compose stop app
Bu adım atlanamaz. Uygulama açıkken geri yüklerseniz, açılışta çalışan migration tabloları önceden oluşturur ve pg_restore yüzlerce çakışma hatası yutarak yarı dolu bir veritabanı bırakır. Bu prosedür yazılırken tam olarak bu oldu.

#Adım 2 — veritabanını boşaltın

docker compose exec db psql -U notusship -d postgres -c 'DROP DATABASE notusship;'
docker compose exec db psql -U notusship -d postgres -c 'CREATE DATABASE notusship OWNER notusship;'

#Adım 3 — geri yükleyin

docker compose exec db pg_restore -U notusship -d notusship \
  --no-owner --no-privileges /backups/notusship-20260814-182013.dump

Komut db konteynerinden çalışır; yedek volume'u oraya salt okunur bağlanmıştır. Uygulama konteynerine ihtiyaç yoktur — zaten kapalıdır.

Çıktı boş olmalı. Hata satırı görürseniz devam etmeyin, bölüm 3'e bakın.

#Adım 4 — uygulamayı başlatın

docker compose start app
docker compose logs app --tail 20

Log'da Database is up to date, no migrations to apply. görmelisiniz. Bunun yerine N migration(s) to apply yazıyorsa yedek sizin sürümünüzden eski demektir; bu normaldir ve migration'lar üstüne uygulanır.

Loglar İngilizce, arayüz Türkçe. Operatöre bakan her metin İngilizce sabit (NSHIP-118): bir hata mesajını arama motorunda aratan kişi sonuç bulabilsin diye.

#Adım 5 — doğrulayın

curl -s localhost:8080/health/ready

Sonra arayüzden giriş yapıp bir issue açın. Okuma çalışıp yazma çalışmıyorsa issue sayacı geri gelmemiş olabilir — bölüm 3.


#2b. Geri yükleme — harici PostgreSQL kullanıyorsanız

db konteyneri yok, dolayısıyla pg_restore'u uygulama konteyneri çalıştırır; yedek dizini zaten orada bağlı.

Sıra biraz farklı: uygulamayı durduramazsınız, çünkü komutu çalıştıracak konteyner o. Bunun yerine --clean --if-exists kullanılır — geri yükleme, açılışta migration'ların oluşturduğu nesneleri düşürüp yerine yedektekileri koyar.

# 1) Yedeği seçin
docker compose exec app ls -lh /data/backups

# 2) Geri yükleyin (uygulama AYAKTA, --clean şart)
docker compose exec app sh -c 'PGPASSWORD=<şifre> pg_restore \
  --host <db-adresi> --port 5432 --username <kullanıcı> --dbname <veritabanı> \
  --no-owner --no-privileges --clean --if-exists /data/backups/<dosya>.dump'

# 3) Uygulamayı yeniden başlatın — bağlantı havuzu bayat tablolara bakıyor
docker compose restart app

Log'da Database is up to date, no migrations to apply. görmelisiniz.

--clean --if-exists olmadan yüzlerce "already exists" hatası alır ve yarı dolu bir veritabanıyla kalırsınız. Paket kurulumunda bu gerekmiyor çünkü orada uygulamayı durdurup boş veritabanına geri yükleyebiliyoruz (§2).

Denendi: 16 Ağustos 2026, gerçek harici PostgreSQL. Veritabanı DROP edildi, uygulama konteynerinden geri yüklendi, kullanıcı yeniden giriş yaptı, migration geçmişi yerindeydi.


#3. Sık karşılaşılan sorunlar

BelirtiSebepÇözüm
pg_restore: error: could not execute query: ERROR: relation already existsUygulama kapatılmadan geri yüklendiAdım 1'den itibaren baştan yapın
pg_dump: error: aborting because of server version mismatchİmajdaki istemci sunucudan eskiİmajı yükseltin; uygulama bunu başlangıçta da log'a yazar
Yedek dosyası 0 baytpg_dump başarısız olduBu dosyayı kullanmayın; iş yarım dosyayı siler, elle alınanı silmez
Giriş çalışıyor ama yeni issue açılamıyorYarım geri yüklemeBaştan geri yükleyin
Geri yükleme sonrası ekler açılmıyornotusship-files geri yüklenmediDosya volume'unu da geri yükleyin

#4. Yapılan tatbikat (14 Ağustos 2026)

Kurulum: temiz docker compose up -d, ilk kullanıcı kaydı, bir proje (GERI), bir issue (GERI-1).

1. backup-now                    → notusship-20260814-182013.dump (77 KB, 0.07 sn)
2. docker compose stop app       → durdu
3. DROP DATABASE notusship       → public şemada 0 tablo kaldı
4. pg_restore …                  → çıkış kodu 0, hata yok
5. docker compose start app      → "Database is up to date, no migrations to apply."

Doğrulama:

KontrolSonuç
Giriş200
Proje listesiGERI · Geri Yükleme Testi
IssueGERI-1 · Bu issue geri yüklenmeli · Bug
Geri yüklemeden sonra yazmaGERI-2 oluştu — issue sayacı da doğru geri geldi

Sayacın doğru dönmesi önemliydi: projects.issue_counter geri gelmeseydi yeni issue'lar var olan anahtarlarla çakışırdı ve bu ancak günler sonra fark edilirdi.


#5. Yükseltme

docker compose exec app dotnet NotusShip.Api.dll backup-now   # önce yedek
docker compose pull
docker compose up -d
docker compose logs app --tail 30                              # migration'ları izleyin

Yükseltmeden sonra kullanıcı beyaz ekran + "Beklenmeyen bir hata oluştu" görüyorsa: tarayıcısında bu sürümden önce alınmış bir index.html var. Bir kez sert yenileme (Cmd/Ctrl + Shift + R) çözer ve tekrarlamaz.

Migration'lar açılışta otomatik uygulanır ve log'a yazılır. Kolon silme/yeniden adlandırma iki aşamada yapıldığı için (CLAUDE.md kural 5) bir sürüm geri dönmek veri kaybetmez — ama yine de önce yedek alın.