HomeBlogRestic + Backblaze B2 ile Homelab Yedekleme: 1 Yıllık Notlarım ve 2 Gerçek Restore
Developer Tools

Restic + Backblaze B2 ile Homelab Yedekleme: 1 Yıllık Notlarım ve 2 Gerçek Restore

Emre Ferit AslantasMay 27, 202612 dkArticle
restic backblaze-b2 homelab backup disaster-recovery self-hosted
Sponsored

Bir yıl önce evimdeki Proxmox sunucusu ile Mac Mini M4 arasında dağılmış olan veriyi tek bir yerden yedeklemeye karar verdim. O zamana kadar rsync + harici disk şeklinde işleyen bir akışım vardı, ama haftada bir hatırlamak ve diski çıkarıp sokmak insan iradesine bağımlıydı; iki ay sonra unutmaya başladım. Bir sabah CT102'deki MCP Hub konfigürasyonunun yarısını yanlışlıkla sildiğimde "yedeği var mıydı acaba" diye düşünmek, kararı kendiliğinden verdirdi.

Bu yazı, Restic'i son 12 ayda nasıl kullandığımı; iki gerçek restore yaptığımı; Borg'tan neden geçtiğimi ve Backblaze B2'nin ay sonunda kart ekstresinde nasıl göründüğünü dürüst şekilde anlatıyor. Reklamı yapılmış sayılar değil, kendi restic snapshots çıktımdan okuduğum rakamlar.

Neden Restic? Borg ile Üç Hafta Sonra Vazgeçtim

İlk denemem Borg Backup olmuştu. Topluluk büyük, dökümantasyon ciddi, deduplikasyon harika. Üç hafta kullandıktan sonra iki sebepten Restic'e geçtim:

  1. Borg'un native B2 desteği yok. borg sadece SSH üzerinden remote repo kullanabiliyor; B2'ye yazmak için rclone mount veya borgbackup-s3 gibi katmanlar gerekiyor. Her ek katman, gece 03:00'te patlayacak ve uyandırmayacak bir noktadır.
  2. Restic tek binary, Go ile statically linked. macOS host'umda Homebrew ile, Proxmox CT'lerimde .deb ile, hepsi aynı binary davranışı. Bir yerde test ettiğim komut diğer yerde de aynen çalışıyor.

Borg muhtemelen tek bir Linux sunucu yedekleyen biri için hâlâ daha temizdir. Ama benim kullanım profilim "üç farklı OS, iki farklı CPU mimarisi, B2'ye yaz" idi. Restic bu profile daha az direnç gösterdi.

Yedeklenen Şeyler ve Boyutlar

Bir yıl sonra restic stats çıktısı:

Total File Count:   1,847,392
Total Size:         118.6 GiB
Snapshots:          76 (1.2 yıl, daily + weekly prune sonrası)

Ne yedekliyorum, niye yedekliyorum:

| Kaynak | Boyut | Sıklık | Neden | |---|---|---|---| | Mac Mini ~/projects | ~24 GB | Daily | Aktif kod, dotfiles, notlar | | Mac Mini ~/notes | ~80 MB | Daily | Lessons.md, todo.md, infra reference | | Proxmox CT konfigürasyonları (/etc/pve/lxc/*.conf) | ~12 KB | Daily | Yeniden kurulumda altın değerinde | | CT100 AdGuard config + listeler | ~150 MB | Daily | DNS overrides, custom blocklist | | CT102 MCP Hub config | ~40 MB | Daily | API key'ler değil — sadece server tanımları (key'ler Vault'ta) | | Docker volumes (Redis dump, ChromaDB) | ~6 GB | Daily | K8s ai-lab namespace state | | ~/Pictures (selective) | ~85 GB | Weekly | Aile fotoğrafları — günlük değişmiyor, haftalık yeter |

Toplam yedek hacminin %72'si fotoğraflar. Geri kalan tüm operasyonel veri 33 GB civarında. Bu rakamı görünce "neyi yedekliyorum" sorusunu daha disiplinli sormaya başladım — boyutu büyüten her şey "haftada bir mi yoksa günde bir mi gerek?" filtresinden geçti.

Setup: B2 Bucket + Repo Init

Backblaze B2 hesabı açıp efa-homelab-backup adında bir bucket oluşturdum. Bucket'ı private ve "no application keys other than this one" olarak kıstım. Application key'i sadece bu bucket'a erişebilecek şekilde dar tuttum — yetkileri minimize etmek, bir günkü "key sızdı mı" panik anında uyumayı kolaylaştırıyor.

İlk repo initialize:

export B2_ACCOUNT_ID="<keyID>"
export B2_ACCOUNT_KEY="<applicationKey>"
export RESTIC_REPOSITORY="b2:efa-homelab-backup:/restic"
export RESTIC_PASSWORD_FILE="/etc/restic/.passphrase"

restic init

RESTIC_PASSWORD_FILE içindeki passphrase'i ayrıca iki yerde offline tutuyorum: kağıt üzerinde bir kasada ve eşimin bir 1Password vault'ında. Restic repo passphrase'ini kaybederseniz B2'deki 118 GB'ınız temaşadan ibaret kalır. Bir keresinde bunu unutmuş insanlar için Reddit thread'lerini okuyup geceyi geçirmek bu paranoyaya yetti.

Cron + Pre/Post Hook'lar

Mac Mini'de launchd agent yerine basit bir cron job kullandım, çünkü gece 03:00'te bilgisayar uyuyor olabilir; bunun yerine pmset ile uyanma planı kurdum:

# /etc/cron.d/restic-daily (Mac Mini)
0 3 * * * efa /usr/local/bin/restic-backup-daily.sh >> /var/log/restic.log 2>&1

Script'in iskeleti:

#!/usr/bin/env bash
set -euo pipefail
source /etc/restic/env

# 1. Kritik servisleri quiesce et
docker exec ai-lab-redis-master-0 redis-cli BGSAVE || true
sleep 5

# 2. Backup
restic backup \
  --tag daily \
  --exclude-caches \
  --exclude='node_modules' \
  --exclude='.venv' \
  --exclude='*.tmp' \
  /Users/efa/projects \
  /Users/efa/notes \
  /var/lib/docker/volumes/ai-lab_redis-data \
  /var/lib/docker/volumes/ai-lab_chromadb-data

# 3. Eski snapshot'lari budama (forget + prune)
restic forget \
  --keep-daily 7 \
  --keep-weekly 4 \
  --keep-monthly 12 \
  --prune

# 4. Sessizce ölmesini engelle
if [ $? -ne 0 ]; then
  curl -X POST "https://uptime.efa.local/api/push/restic-mac" \
    -d "status=down&msg=restic_backup_failed"
fi

exclude='node_modules' satırını eklemeden önceki ilk haftalık snapshot 41 GB idi. Eklendikten sonra 9 GB'a düştü. JavaScript ekosistemini yedeklememek, B2 faturanızın en hızlı düşeceği yerdir.

Proxmox tarafında ise restic-backup-daily.sh script'i her CT için aynı pattern ile çalışıyor; CT100/CT102'nin konfig dizinleri ve volume mount'ları. Aşağıdaki satır en kritik öğretici parçaydı:

restic backup --host proxmox-ct102 --tag ct102 /var/lib/lxc/102/rootfs/opt/mcp-hub

--host flag'i olmadan, tüm makineler snapshot listesinde tek bir hostname altında karışıyor ve hangi makineyi restore ettiğinizi bulmak hâle dönüşüyor. Bu detayı ikinci ayda öğrendim, geri dönüp eski snapshot'ları rename edemezsiniz — yeni mantıkla yaşamak zorunda kaldım.

İlk Gerçek Restore: PostgreSQL Migration Fiyaskosu

İlk gerçek restore'um Şubat'taydı. EFA Agent için bir migration script'i yazdım, users tablosunun bir kolonunu rename ediyordu. Migration test ortamında çalıştı; production'da (yani K8s ai-lab namespace'imde) bir kosa daha eklemişim — DROP COLUMN yazmıştım, RENAME yerine.

Tablonun 4 saatlik veri kaybı vardı. Süreç:

# 1. PostgreSQL podunu durdur
kubectl scale -n ai-lab statefulset postgres-master --replicas=0

# 2. Restic'ten o sabahki snapshot'i bul
restic snapshots --tag daily --host mac-mini-m4 \
  --path /var/lib/docker/volumes/ai-lab_postgres-data

# ID: 8f3a2c1e (2026-02-14 03:00:14)

# 3. Tek bir path'i restore et
restic restore 8f3a2c1e \
  --target /tmp/restore-postgres \
  --include /var/lib/docker/volumes/ai-lab_postgres-data

# 4. Mevcut volume'u backup'la (paranoid step)
mv /var/lib/docker/volumes/ai-lab_postgres-data \
   /var/lib/docker/volumes/ai-lab_postgres-data.broken

# 5. Restored datayi yerine koy
mv /tmp/restore-postgres/var/lib/docker/volumes/ai-lab_postgres-data \
   /var/lib/docker/volumes/

# 6. Statefulset'i tekrar baslat
kubectl scale -n ai-lab statefulset postgres-master --replicas=1

Toplam süre: 8 dakika. Restic, B2'den yalnızca değişen blob'ları indirdi (~600 MB). 100 Mbps upload + 200 Mbps download fiberim için kabul edilebilir bir indirme süresi.

Kaybettiğim 4 saatlik veri vardı, çünkü gece 03:00 snapshot'ı sonra olmuş şeyleri içermiyordu. Bu beni daha sık snapshot almaya değil, migration'ları çalıştırmadan önce manuel snapshot tetiklemeye yöneltti:

restic backup --tag pre-migration --tag manual /var/lib/docker/volumes/ai-lab_postgres-data

İkinci Restore: Yanlışlıkla Silinen Notlar Klasörü

İkinci restore basit bir kullanıcı hatasıydı. ~/notes klasörünü notes-archive'a taşımak isterken mv notes ../ yazıp Enter'a basmadan önce bir başka terminale tıkladım. Süpürmeye çalıştığım git repo başka bir klasörde silinmişti.

restic restore latest --target /tmp/notes-restore \
  --include /Users/efa/notes

90 saniye. Sonra rsync -a /tmp/notes-restore/Users/efa/notes/ ~/notes/. Restic'in latest keyword'ü bu tür panik anlarında düşünmeyi azaltıyor.

Aylık Backblaze B2 Faturası: Gerçek Sayılar

Son 6 ayın ortalaması:

| Kalem | Boyut/Hacim | Aylık $ | |---|---|---| | Storage (B2) | 118 GB | $0.71 | | Class B transactions (read, restore sırasında) | ~12k | $0.05 | | Class C transactions (write, daily backups) | ~280k | $0.11 | | Egress (restore: ~3 GB toplam 6 ayda) | 0.5 GB ort. | $0.05 | | TOPLAM | — | ~$0.92/ay |

Yıllık ~$11. Cloud yedek için bu rakam, "yedek almıyorum çünkü pahalı" mazeretini ortadan kaldırıyor. Karşılaştırma için, Google One 200 GB planı $30/yıl ve restic ile native kullanılamıyor.

Not: İlk ay biraz daha yüksekti (~$1.40) çünkü full ilk backup ile B2'ye 118 GB upload sırasında daha fazla Class C transaction oluyor. Steady state'e 2-3 ay sonra oturdu.

Vazgeçtiğim ve İşe Yaramayan Üç Deneme

1. restic mount ile Time Machine Benzeri Browse

Restic'in mount komutu, FUSE üzerinden snapshot'ları dosya sistemi olarak gösteriyor. Time Machine gibi gezinip restore yapabileceğimi düşündüm. macOS'ta macFUSE'un sistem extension policy'leri ile boğuştuktan sonra vazgeçtim. Apple Silicon'da macFUSE her macOS güncellemesinde reauthorize gerektiriyor. Komut satırından restic restore --include ile spesifik path restore etmek bana yetiyor; bir GUI ihtiyacım yok.

2. Restic'in Built-in --read-concurrency Tuning'i

İnternette "B2 için --read-concurrency 16 ve --pack-size 64 yapın hız 2x artar" gibi tavsiyelere uydum. Hiçbir gözlemlenebilir fark olmadı, bandwidth limitim zaten upstream'imdi (100 Mbps). Network-bound bir iş yükünde concurrency tuning marjinal. Önce darboğazı ölçün — iftop veya nettop 30 saniye yetiyor — sonra parametre değiştirin.

3. Sadece Restic'e Güvenmek (Offsite-Only Strategy)

İlk altı ay tek hedefim B2 idi. Bir "ya B2 hesabım bir gün suspend olursa?" paranoyası beni 3-2-1 yedek kuralına geri çekti. Şimdi Restic'i aynı anda iki repo'ya yazıyor: B2 ve evdeki bir USB SSD (Mac Mini'ye bağlı, gece bağlanıp sabah çıkarılan değil — sürekli bağlı, çünkü "manuel rotation" insan dayanıklılığına bağımlıdır). Local restore, ortalama 50 MB/s ile B2'den çok daha hızlı:

# Iki ayri kosma, ayni source
restic -r b2:efa-homelab-backup:/restic backup ...
restic -r /Volumes/BackupSSD/restic backup ...

Hafif duplikasyon var ama disk ucuz. Bant genişliği değil.

Bir Yılın Çıkardığı Notlar

  1. Snapshot test etmediğiniz yedek değil, umut. Üç ayda bir random snapshot seçip random path restore ediyorum. restic check --read-data-subset=5% ayda bir çalışıyor, repo bütünlüğü için.
  2. exclude listesini erken büyütün. node_modules, .venv, __pycache__, *.log — bunlar B2 faturanızı şişirir ve restore'da gereksiz veri indirir.
  3. --tag ile snapshot'ları sınıflandırın. daily, pre-migration, manual tag'leri 1 yıl sonra hangi snapshot'a bakacağınızı bulmayı saniyeler meselesi yapıyor.
  4. Password'ü iki yerde offline tutun. Bunu söyledim ama tekrar söylüyorum. Repo passphrase = veri demektir. Disk = veri değil.
  5. Restore'u önceden prova edin. İlk panik anınız, ilk restic restore denemenizin yapıldığı an OLMASIN. Cuma akşamı yarım saat ayırın, test edin.

Bir Sonraki Adımım

Önümüzdeki ay restic-server'ı bir Hetzner Storage Box'a kurmayı planlıyorum. B2 ucuz ama bandwidth artışı ile birlikte upload pencerelerim daralıyor; restic-server üzerinden incremental upload daha verimli olabilir. Hâlâ kararsızım, çünkü "çalışan bir sisteme dokunmama" disiplinim de var — ama 6 ay sonra büyüyecek backup hacmi için planlama yapmamak yine bir başka tür kibir olurdu.

Eğer kendi homelab yedek setup'ınızı paylaşmak isterseniz GitHub'dan ulaşın; özellikle restore senaryolarınızı merakla okurum. Yedek yapmanın cron tarafı kolay; yedek-aldığını-test-etmek tarafı blog yazılarında nadir görülen kısım.

Sponsored

Weekly DevOps Newsletter

Get new tool reviews, comparisons and DevOps trends delivered to your inbox weekly.