feat(yedek): günlük MongoDB yedeği, geri yükleme ve bu bilgisayara kopya
- scripts/backup.sh: mongodump + bütünlük denetimi, son 14 yedek - scripts/restore.sh: ayrı veritabanına geri yükleme ve sayım karşılaştırma - scripts/backup-pull.sh + systemd zamanlayıcıları; kurulum docs/DEPLOY.md §6 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,8 @@ npm test # backend (cargo test, MongoDB açık olmalı) + frontend (
|
||||
npm run lint # clippy + oxlint
|
||||
npm run types # Rust DTO → frontend/src/types/*.ts (ts-rs; cargo test de üretir)
|
||||
npm run icons # frontend/brand/*.svg → favicon, PWA, Apple, Tauri ikonları (rsvg-convert + magick)
|
||||
npm run db:backup # kakeibo_dev → ./backups (canlı yedek: docs/DEPLOY.md §6)
|
||||
npm run db:restore -- <dosya|latest> <hedef_db> [--force]
|
||||
```
|
||||
- `npm run dev` öncesinde DB otomatik başlar (`predev`).
|
||||
- Yerel MongoDB host portu **27020** (27017 sistem mongod, 27018/27019 diğer projeler).
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Bu bilgisayar: ~/.config/systemd/user/kakeibo-backup-pull.service
|
||||
[Unit]
|
||||
Description=Kakeibo yedeklerini sunucudan çek
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
# ~/.ssh/config'teki sunucu adı "atlas" değilse başındaki # işaretini kaldırıp düzeltin
|
||||
#Environment=KAKEIBO_BACKUP_HOST=atlas
|
||||
ExecStart=%h/Developer/Rust/kakeibo/scripts/backup-pull.sh
|
||||
@@ -0,0 +1,12 @@
|
||||
# Bu bilgisayar: ~/.config/systemd/user/kakeibo-backup-pull.timer
|
||||
[Unit]
|
||||
Description=Kakeibo yedeklerini her gün çek
|
||||
|
||||
[Timer]
|
||||
# Sunucudaki yedekten sonra; bilgisayar kapalıysa açıldıktan 10 dk sonra
|
||||
OnCalendar=*-*-* 09:00:00
|
||||
OnStartupSec=10min
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,13 @@
|
||||
# Atlas (sunucu): /etc/systemd/system/kakeibo-backup.service
|
||||
[Unit]
|
||||
Description=Kakeibo MongoDB günlük yedeği
|
||||
Wants=docker.service
|
||||
After=docker.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
# Yedeği çeken SSH kullanıcısı root değilse: başındaki # işaretini kaldırıp grubunu yazın.
|
||||
#Environment=BACKUP_GROUP=<ssh-kullanıcınız>
|
||||
ExecStart=/usr/local/lib/kakeibo/backup.sh
|
||||
Nice=10
|
||||
IOSchedulingClass=idle
|
||||
@@ -0,0 +1,12 @@
|
||||
# Atlas (sunucu): /etc/systemd/system/kakeibo-backup.timer
|
||||
[Unit]
|
||||
Description=Kakeibo yedeği her gece 03:30
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 03:30:00
|
||||
# Sunucu o saatte kapalıysa açılınca hemen çalışır
|
||||
Persistent=true
|
||||
RandomizedDelaySec=5m
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
+76
-2
@@ -1,6 +1,6 @@
|
||||
# 🚀 Canlıya Alma (Atlas Homeserver + Dokploy)
|
||||
|
||||
> Durum: ✅ Yayında — https://kakeibo.ta1sph.website (2026-11-12) · Yedekleme ⏳ (Faz 5 bitmeden)
|
||||
> Durum: ✅ Yayında — https://kakeibo.ta1sph.website (2026-11-12) · Yedekleme: betikler hazır, sunucuda kurulum bekliyor (§6)
|
||||
|
||||
## 1. Mimari
|
||||
|
||||
@@ -95,4 +95,78 @@ Notlar:
|
||||
|
||||
## 6. Yedekleme ve geri yükleme
|
||||
|
||||
> Faz 3 – Adım 5'te doldurulacak (günlük `mongodump`, son 14 yedek).
|
||||
```
|
||||
Atlas 03:30 backup.sh ──▶ /var/backups/kakeibo/kakeibo-YYYY-MM-DD_HHMM.archive.gz (son 14)
|
||||
│ rsync (SSH)
|
||||
Bu bilgisayar 09:00 / açılışta ──▶ ~/Yedekler/kakeibo/ (son 60, 2 günden eskiyse masaüstü uyarısı)
|
||||
```
|
||||
|
||||
| Dosya | Nerede çalışır | İş |
|
||||
|---|---|---|
|
||||
| `scripts/backup.sh` | Atlas → `/usr/local/lib/kakeibo/` | `mongodump --archive --gzip` → gzip + `mongorestore --dryRun` denetimi → son 14'ü tutar |
|
||||
| `scripts/restore.sh` | Atlas (ve yerelde `npm run db:restore`) | yedeği **başka adla** geri yükler, kayıt sayılarını gösterir/karşılaştırır |
|
||||
| `scripts/backup-pull.sh` | bu bilgisayar | yeni yedekleri çeker, eskiyse uyarır |
|
||||
| `deploy/systemd/*` | ikisi | zamanlayıcılar (`Persistent=true`: kapalıyken kaçan çalışma açılınca yapılır) |
|
||||
|
||||
Yerel deneme: `npm run db:backup` (kakeibo_dev → `./backups`, git'e girmez) ·
|
||||
`COMPARE_DB=kakeibo_dev npm run db:restore -- latest kakeibo_restore_test`.
|
||||
|
||||
### 6.1 Atlas'ta kurulum (bir kez)
|
||||
Bu bilgisayarda, proje klasöründe:
|
||||
```bash
|
||||
scp scripts/backup.sh scripts/restore.sh deploy/systemd/kakeibo-backup.service deploy/systemd/kakeibo-backup.timer atlas:/tmp/
|
||||
ssh atlas
|
||||
```
|
||||
Atlas'ta:
|
||||
```bash
|
||||
sudo install -Dm755 /tmp/backup.sh /usr/local/lib/kakeibo/backup.sh
|
||||
sudo install -Dm755 /tmp/restore.sh /usr/local/lib/kakeibo/restore.sh
|
||||
sudo install -m644 /tmp/kakeibo-backup.service /tmp/kakeibo-backup.timer /etc/systemd/system/
|
||||
```
|
||||
- SSH'ye **root olmayan** bir kullanıcıyla bağlanıyorsanız: `sudo systemctl edit --full kakeibo-backup.service` →
|
||||
`#Environment=BACKUP_GROUP=…` satırının başındaki `#`'i kaldırıp kendi kullanıcı grubunuzu yazın (yoksa yedekleri çekemezsiniz).
|
||||
|
||||
İlk yedek ve kontrol:
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl start kakeibo-backup.service
|
||||
journalctl -u kakeibo-backup -n 20 --no-pager # "tamam: …" görülmeli
|
||||
```
|
||||
|
||||
### 6.2 Geri yükleme denemesi (kurulumda bir kez, sonra ayda bir önerilir)
|
||||
Canlı veriye dokunmaz; ayrı `kakeibo_restore_test` veritabanına yükler ve sayıları karşılaştırır:
|
||||
```bash
|
||||
sudo COMPARE_DB=kakeibo /usr/local/lib/kakeibo/restore.sh latest kakeibo_restore_test
|
||||
# "✅ kakeibo ile aynı" görülmeli. Sonra deneme veritabanını silin:
|
||||
sudo docker exec $(sudo docker ps -q -f name=kakeibo-kakeibomongo-fibzhc) mongosh --quiet kakeibo_restore_test --eval 'db.dropDatabase()'
|
||||
```
|
||||
Sonra zamanlayıcıyı açın:
|
||||
```bash
|
||||
sudo systemctl enable --now kakeibo-backup.timer
|
||||
systemctl list-timers kakeibo-backup.timer # bir sonraki çalışma saati
|
||||
```
|
||||
|
||||
### 6.3 Bu bilgisayarda (sunucu dışı kopya)
|
||||
Önce şifresiz SSH çalışmalı: `ssh -o BatchMode=yes atlas ls /var/backups/kakeibo` yedek dosyasını listelemeli.
|
||||
```bash
|
||||
scripts/backup-pull.sh # elle bir kez: ~/Yedekler/kakeibo/
|
||||
mkdir -p ~/.config/systemd/user
|
||||
cp deploy/systemd/kakeibo-backup-pull.service deploy/systemd/kakeibo-backup-pull.timer ~/.config/systemd/user/
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now kakeibo-backup-pull.timer
|
||||
```
|
||||
- SSH'de sunucunun adı `atlas` değilse: `systemctl --user edit --full kakeibo-backup-pull.service` → `KAKEIBO_BACKUP_HOST` satırı.
|
||||
- Günlük: `journalctl --user -u kakeibo-backup-pull -n 20`.
|
||||
|
||||
### 6.4 Felaket sonrası geri yükleme (canlı veritabanının yerine)
|
||||
1. Dokploy → `kakeibo-app` → **Stop** (yükleme sırasında yazma olmasın).
|
||||
2. Gerekirse yedeği sunucuya kopyalayın: `scp ~/Yedekler/kakeibo/<dosya> atlas:/tmp/`.
|
||||
3. Atlas'ta: `sudo /usr/local/lib/kakeibo/restore.sh /tmp/<dosya> kakeibo --force` (önce mevcut `kakeibo` silinir).
|
||||
4. `kakeibo-app` → **Start**, `/api/health` ve giriş kontrolü.
|
||||
- MongoDB servisi tamamen yeniden kurulduysa önce §4'teki kullanıcıları (`kakeibo_app`) yeniden oluşturun.
|
||||
- Yedekte kullanıcı şifre özetleri (argon2) ve oturum bilgisi bulunur → yedek dosyalarını paylaşmayın.
|
||||
|
||||
### Notlar
|
||||
- MongoDB'de kimlik doğrulaması zorunlu olmadığından (§4) `mongodump` konteynerin içinden şifresiz çalışır.
|
||||
Auth açıldığında yedek için ayrı bir `backup` rolü kullanıcısı ve betiğe `--config` ile şifre dosyası eklenmeli.
|
||||
- Yedek boyutu şu an birkaç KB–MB; 14 + 60 dosya önemsiz yer tutar.
|
||||
|
||||
+3
-2
@@ -1,7 +1,7 @@
|
||||
# 🧠 Proje Hafızası
|
||||
|
||||
## ▶️ Sıradaki Adım
|
||||
**Faz 5 – Adım 5:** Canlı yedekleme (gerçek veri girişinden önce): Dokploy'da günlük `mongodump --archive --gzip`, 14 gün sakla (eskisini sil), yedeği sunucu dışına da kopyala; boş bir DB'ye `mongorestore` ile geri yükleme denemesi yap ve `docs/DEPLOY.md`'ye adımları yaz. `scripts/backup` yerel karşılığı. Bitince Faz 5 ✅ → Faz 6 (ay sonu değerlendirmesi + dönem kapatma).
|
||||
**Faz 5 – Adım 5 (kullanıcıda):** `docs/DEPLOY.md` §6.1–6.3: Atlas'a betik + systemd timer kurulumu, ilk yedek, `restore.sh latest kakeibo_restore_test` ile karşılaştırma, bu bilgisayarda pull timer. Kullanıcı çıktıları (journal satırları, "✅ kakeibo ile aynı") bildirince Faz 5 ✅ → Faz 6 (ay sonu değerlendirmesi + dönem kapatma/devir).
|
||||
|
||||
## 📌 Alınan Kararlar
|
||||
- **Yerel demo hesapları** (`kakeibo_dev`): `demo@ornek.com` / `demo12345` (owner, "Demo Hanesi") · `es@ornek.com` / `demo12345` (member, aynı hane). `npm run db:reset` silerse API ile yeniden oluştur (ileride `db:seed`).
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
## ❓ Açık Konular
|
||||
- Geliştirme makinesi: CachyOS nodejs 26.13.0-2 ↔ simdjson .so.36 uyumsuzluğu → simdjson 2:5.0.5-1'e düşürüldü (2026-11-12). Düzeltilmiş nodejs gelene kadar `pacman -Syu --ignore simdjson`. `node: libsimdjson.so.34` hatası görülürse sebep budur.
|
||||
- **Canlı yedekleme yok** (Faz 3 Adım 5 ertelendi) → gerçek veri girişinden önce, en geç Faz 5 sonunda: günlük mongodump, 14 yedek, geri yükleme testi.
|
||||
- **Canlı yedekleme kurulumu bekliyor** (betikler hazır, `docs/DEPLOY.md` §6) → gerçek veri girişinden önce.
|
||||
- **Canlı MongoDB auth kapalı** (Dokploy replica set betiği keyFile kullanmıyor, özel Command'ı yok sayıyor). İç ağda, port kapalı. Ayrıntı: `docs/DEPLOY.md` §4.
|
||||
- Üye çıkarma / rol değiştirme / sahipliği devretme henüz yok (Ayarlar ekranıyla birlikte, Faz 2+).
|
||||
- Kapalı dönem yeniden açılırken başka açık dönem varsa davranış (Faz 6).
|
||||
@@ -103,3 +103,4 @@
|
||||
- **Adım 2:** Gerçekleşen harcama (saklanmaz, her istekte aggregation): `GET /summary` → `spent_total`, `remaining` (= harcanabilir − harcanan, negatifse aşılmış). `GET /budget` → grupta `spent` (dönem), haftada `spent`. `spent_by_group_and_date` (grup×gün toplanır, Rust'ta haftalara dağıtılır; tarih metin karşılaştırması). Silinen kayıt sayılmaz, kategori değişince harcama yeni gruba geçer. Test `actual_spending_in_summary_and_budget` (hafta sınırları 6/7 Aralık, dönem son günü, önceki dönem hariç, geri al). Mutasyon: hafta sınırı `<=`→`<` ve silinenleri sayma → ikisi de yakalandı. Backend 98 test.
|
||||
- **Adım 3:** Defter ekranı (`features/ledger/`): `WeekPicker` (◀ ▶, "Bu hafta", haftanın harcanan/bütçe toplamı), `EntryForm` (büyük `MoneyInput`, 4 renkli grup düğmesi, kategori/ödeme türü çipleri, tarih, açıklama; kayıttan sonra tutar+açıklama temizlenir, odak tutara döner), `schema.ts` (`entrySchema(start,last)`, `fieldForError`: sunucu mesajı → alan). Hafta adreste: `/defter?hafta=N` (bugünün haftasında parametre yok); başka haftaya tarihli kayıt → o haftaya geçer; hafta değişince tarih o haftaya çekilir. `lib/ledger.ts` (`weekIndexFor`, `defaultEntryDate`, `parseWeekParam`, son seçim localStorage `kakeibo.ledger.last`: grup + **her grubun son kategorisi** + ödeme türü; pasifleşen seçim düşer). `lib/catalog.ts` (`useCategories`, `usePaymentMethods`, 5 dk staleTime), `lib/entries.ts` (`useEntries`, `useCreateEntry`), `queryKeys.entries`. Frontend 79 test; e2e 31 kontrol (ayrı 8091 sunucu, DB silindi). Mobilde "Beklenmeyen" taşması düzeltildi.
|
||||
- **Adım 4:** Defter listesi ve özet: `entry-list.tsx` (gün gün `groupByDay`, gün adı `formatWeekday` = Intl tr-TR UTC; satır: kategori emojisi grup renginde, grup · ödeme · açıklama; düzenle/sil 44 px), sil → sonner `action` "↩️ Geri al" (8 sn) → `useRestoreEntry`. Düzenleme aynı `EntryForm` (`entry` + `key`, Güncelle/Vazgeç; `version` gönderilir, 409 Türkçe root hatası; `''` → ödeme/açıklama kaldır). Katalog **pasifler dahil** okunur (eski kayıtların adı; seçenekte yalnız aktifler + kaydın mevcut değeri). `week-summary.tsx` grup bazlı bütçe/harcanan/kalan + çubuk, aşımda "Aşıldı" kırmızı. Masaüstü iki sütun (form solda yapışkan); dar sütunda taşmaya karşı **container query** (`@container`, `@lg:grid-cols-4`). Frontend 81 test; e2e 29 kontrol (+ Adım 3'ün 31'i yeniden geçti). **Not:** e2e betikleri aynı DB'yi paylaşırsa birbirini bozar → her biri temiz DB ile.
|
||||
- **Adım 5:** Yedekleme betikleri: `scripts/backup.sh` (Swarm etiketiyle konteyneri bulur, `mongodump --archive --gzip` → `.partial` → `gzip -t` + `mongorestore --dryRun` → ad verilir; son `KEEP=14`, `BACKUP_GROUP` ile okuma izni), `scripts/restore.sh` (dosya adından kaynak DB, `--nsFrom/--nsTo` ile **başka adla**; mevcut hedefe yalnızca `--force` + `--drop`; `COMPARE_DB` ile sayım karşılaştırma), `scripts/backup-pull.sh` (rsync SSH BatchMode, `~/Yedekler/kakeibo`, son 60, 48 saatten eski → `notify-send`). `deploy/systemd/` (sunucu 03:30, bu bilgisayar 09:00 + açılış, `Persistent=true`). Kullanıcı kararı: sunucu dışı kopya **bu bilgisayara** (S3 değil). `npm run db:backup` / `db:restore` (yerel, `backups/` gitignore). Yerel test: yedek → geri yükleme sayıları kakeibo_dev ile aynı, koruma, bozuk dosya hatası, saklama ✅.
|
||||
|
||||
+1
-1
@@ -58,4 +58,4 @@
|
||||
- ✅ 2. Backend: haftalık/aylık gerçekleşen (grup bazlı bütçe–harcanan–kalan), özet ve önerilere gerçekleşen verisi
|
||||
- ✅ 3. Frontend: Defter — hafta seçici + hızlı giriş formu (tutar, 4 grup düğmesi, kategori, ödeme türü, tarih, açıklama; son seçim hatırlanır)
|
||||
- ✅ 4. Frontend: haftanın girişleri (gün gün), düzenle/sil + "Geri al", haftalık özet
|
||||
- 🔄 5. Canlı yedekleme (mongodump günlük, 14 gün) + geri yükleme denemesi
|
||||
- 🔄 5. Canlı yedekleme (betikler + test ✅, sunucuda kurulum kullanıcıda) (mongodump günlük, 14 gün) + geri yükleme denemesi
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
"dev:host": "concurrently -k -n api,web -c blue,green \"npm:dev:api\" \"npm --prefix frontend run dev -- --host\"",
|
||||
"db:up": "docker compose -f docker-compose.dev.yml up -d --wait",
|
||||
"db:down": "docker compose -f docker-compose.dev.yml stop",
|
||||
"db:backup": "MONGO_CONTAINER=kakeibo-mongo DB_NAME=kakeibo_dev BACKUP_DIR=./backups scripts/backup.sh",
|
||||
"db:restore": "MONGO_CONTAINER=kakeibo-mongo BACKUP_DIR=./backups scripts/restore.sh",
|
||||
"db:seed": "echo 'Seed betiği Faz 1-4 arasında eklenecek.'",
|
||||
"db:reset": "docker exec kakeibo-mongo mongosh --quiet --eval \"db.getSiblingDB('kakeibo_dev').dropDatabase(); db.adminCommand({listDatabases:1}).databases.filter(d => d.name.startsWith('kakeibo_test_')).forEach(d => db.getSiblingDB(d.name).dropDatabase()); print('kakeibo_dev ve test veritabanları sıfırlandı.')\"",
|
||||
"test": "cd backend && cargo test && cd .. && npm --prefix frontend test",
|
||||
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canlı yedekleri bu bilgisayara çeker (sunucu dışı kopya). systemd kullanıcı zamanlayıcısı çalıştırır;
|
||||
# elle de çalıştırılabilir: scripts/backup-pull.sh
|
||||
#
|
||||
# KAKEIBO_BACKUP_HOST ~/.ssh/config'teki sunucu adı [atlas]
|
||||
# REMOTE_DIR Sunucudaki yedek klasörü [/var/backups/kakeibo]
|
||||
# LOCAL_DIR Bu bilgisayardaki klasör [~/Yedekler/kakeibo]
|
||||
# KEEP Burada saklanacak yedek sayısı [60]
|
||||
#
|
||||
# En yeni yedek 2 günden eskiyse masaüstü bildirimi verir (sunucudaki günlük yedek durmuş olabilir).
|
||||
set -euo pipefail
|
||||
|
||||
HOST=${KAKEIBO_BACKUP_HOST:-atlas}
|
||||
REMOTE_DIR=${REMOTE_DIR:-/var/backups/kakeibo}
|
||||
LOCAL_DIR=${LOCAL_DIR:-$HOME/Yedekler/kakeibo}
|
||||
KEEP=${KEEP:-60}
|
||||
|
||||
warn() {
|
||||
echo "[kakeibo-pull] UYARI: $*" >&2
|
||||
command -v notify-send >/dev/null && notify-send -u critical "Kakeibo yedeği" "$*" || true
|
||||
}
|
||||
|
||||
mkdir -p "$LOCAL_DIR"
|
||||
chmod 700 "$LOCAL_DIR"
|
||||
# Yalnızca tamamlanmış yedekler (.partial hariç); var olanlar yeniden indirilmez
|
||||
if ! rsync -a --ignore-existing --include='*.archive.gz' --exclude='*' \
|
||||
-e 'ssh -o BatchMode=yes -o ConnectTimeout=15' "$HOST:$REMOTE_DIR/" "$LOCAL_DIR/"; then
|
||||
warn "sunucudan yedek çekilemedi ($HOST). Sunucu açık mı, SSH anahtarı çalışıyor mu?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
newest=$(ls -1t "$LOCAL_DIR"/*.archive.gz 2>/dev/null | head -n1 || true)
|
||||
if [[ -z $newest ]]; then
|
||||
warn "hiç yedek yok"
|
||||
exit 1
|
||||
fi
|
||||
age_h=$(( ($(date +%s) - $(stat -c %Y "$newest")) / 3600 ))
|
||||
echo "[kakeibo-pull] en yeni: $(basename "$newest") ($age_h saat önce)"
|
||||
(( age_h <= 48 )) || warn "en yeni yedek $age_h saat önce alınmış; sunucudaki günlük yedeği kontrol edin."
|
||||
|
||||
ls -1t "$LOCAL_DIR"/*.archive.gz | tail -n +"$((KEEP + 1))" | while read -r f; do rm -f -- "$f"; done
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
# Kakeibo veritabanı yedeği: MongoDB konteynerinde mongodump → sıkıştırılmış tek dosya.
|
||||
#
|
||||
# Canlı (Atlas, root): /usr/local/lib/kakeibo/backup.sh
|
||||
# Yerel: npm run db:backup (kakeibo_dev → ./backups)
|
||||
#
|
||||
# Ayarlar (ortam değişkeni):
|
||||
# MONGO_SERVICE Dokploy/Swarm servis adı (konteyner bundan bulunur) [kakeibo-kakeibomongo-fibzhc]
|
||||
# MONGO_CONTAINER Konteyner adı doğrudan (verilirse MONGO_SERVICE kullanılmaz)
|
||||
# DB_NAME Yedeklenecek veritabanı [kakeibo]
|
||||
# BACKUP_DIR Yedek klasörü [/var/backups/kakeibo]
|
||||
# KEEP Saklanacak yedek sayısı (en yeniler) [14]
|
||||
# BACKUP_GROUP Verilirse klasör/dosyalar bu gruba okunur yapılır (yedeği çeken SSH kullanıcısı için)
|
||||
#
|
||||
# Her yedek: yazılır → gzip bütünlüğü + mongorestore --dryRun ile denetlenir → ancak sonra adı verilir.
|
||||
# Hata olursa çıkış kodu ≠ 0 (systemd günlüğünde görünür), yarım dosya kalmaz.
|
||||
set -euo pipefail
|
||||
|
||||
MONGO_SERVICE=${MONGO_SERVICE:-kakeibo-kakeibomongo-fibzhc}
|
||||
DB_NAME=${DB_NAME:-kakeibo}
|
||||
BACKUP_DIR=${BACKUP_DIR:-/var/backups/kakeibo}
|
||||
KEEP=${KEEP:-14}
|
||||
|
||||
log() { echo "[kakeibo-backup] $*"; }
|
||||
die() { echo "[kakeibo-backup] HATA: $*" >&2; exit 1; }
|
||||
|
||||
container=${MONGO_CONTAINER:-}
|
||||
if [[ -z $container ]]; then
|
||||
container=$(docker ps -q -f "label=com.docker.swarm.service.name=$MONGO_SERVICE" | head -n1)
|
||||
[[ -n $container ]] || container=$(docker ps -q -f "name=$MONGO_SERVICE" | head -n1)
|
||||
fi
|
||||
[[ -n $container ]] || die "MongoDB konteyneri bulunamadı ($MONGO_SERVICE). Servis çalışıyor mu?"
|
||||
|
||||
umask 027
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
if [[ -n ${BACKUP_GROUP:-} ]]; then
|
||||
chgrp "$BACKUP_GROUP" "$BACKUP_DIR"
|
||||
chmod 750 "$BACKUP_DIR"
|
||||
fi
|
||||
|
||||
stamp=$(date +%Y-%m-%d_%H%M)
|
||||
file="$BACKUP_DIR/$DB_NAME-$stamp.archive.gz"
|
||||
tmp="$file.partial"
|
||||
trap 'rm -f "$tmp"' EXIT
|
||||
|
||||
log "$DB_NAME yedekleniyor → $file"
|
||||
docker exec "$container" mongodump --db="$DB_NAME" --archive --gzip --quiet > "$tmp" \
|
||||
|| die "mongodump başarısız"
|
||||
[[ -s $tmp ]] || die "yedek dosyası boş"
|
||||
gzip -t "$tmp" || die "yedek dosyası bozuk (gzip)"
|
||||
docker exec -i "$container" mongorestore --archive --gzip --dryRun --quiet \
|
||||
--nsInclude="$DB_NAME.*" < "$tmp" || die "yedek okunamadı (mongorestore --dryRun)"
|
||||
|
||||
mv "$tmp" "$file"
|
||||
[[ -n ${BACKUP_GROUP:-} ]] && chgrp "$BACKUP_GROUP" "$file"
|
||||
log "tamam: $(du -h "$file" | cut -f1)"
|
||||
|
||||
# Eski yedekler: en yeni $KEEP tanesi kalır (gün atlansa da sayı korunur)
|
||||
mapfile -t old < <(ls -1t "$BACKUP_DIR"/"$DB_NAME"-*.archive.gz | tail -n +"$((KEEP + 1))")
|
||||
for f in "${old[@]}"; do
|
||||
rm -f -- "$f"
|
||||
log "silindi (eski): $(basename "$f")"
|
||||
done
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# Yedeği geri yükler ve koleksiyon sayılarını gösterir.
|
||||
#
|
||||
# restore.sh <yedek.archive.gz | latest> <hedef_db> [--force]
|
||||
#
|
||||
# - Yedek hangi DB'den alındıysa (dosya adının başı, ör. "kakeibo-…") ondan <hedef_db>'ye aktarılır.
|
||||
# - Deneme için ayrı bir hedef kullanın (ör. kakeibo_restore_test); bitince silin:
|
||||
# docker exec <konteyner> mongosh --quiet kakeibo_restore_test --eval 'db.dropDatabase()'
|
||||
# - Hedef **canlı veritabanının kendisiyse** (felaket sonrası) `--force` gerekir; hedef önce silinir (--drop).
|
||||
# - COMPARE_DB=kakeibo verilirse hedefteki sayılar o veritabanıyla karşılaştırılır (fark → çıkış kodu 1).
|
||||
#
|
||||
# Konteyner backup.sh ile aynı ayarlarla bulunur (MONGO_SERVICE / MONGO_CONTAINER, BACKUP_DIR).
|
||||
set -euo pipefail
|
||||
|
||||
MONGO_SERVICE=${MONGO_SERVICE:-kakeibo-kakeibomongo-fibzhc}
|
||||
BACKUP_DIR=${BACKUP_DIR:-/var/backups/kakeibo}
|
||||
die() { echo "[kakeibo-restore] HATA: $*" >&2; exit 1; }
|
||||
|
||||
[[ $# -ge 2 ]] || die "kullanım: restore.sh <yedek|latest> <hedef_db> [--force]"
|
||||
src=$1 target=$2 force=${3:-}
|
||||
if [[ $src == latest ]]; then
|
||||
src=$(ls -1t "$BACKUP_DIR"/*.archive.gz 2>/dev/null | head -n1) || true
|
||||
[[ -n $src ]] || die "$BACKUP_DIR içinde yedek yok"
|
||||
fi
|
||||
[[ -f $src ]] || die "dosya yok: $src"
|
||||
[[ $target =~ ^[A-Za-z0-9_-]+$ ]] || die "hedef veritabanı adı geçersiz: $target"
|
||||
|
||||
# Dosya adı: <db>-YYYY-MM-DD_HHMM.archive.gz
|
||||
base=$(basename "$src")
|
||||
from=${base%-????-??-??_????.archive.gz}
|
||||
[[ $from != "$base" ]] || die "dosya adından kaynak veritabanı anlaşılamadı: $base"
|
||||
|
||||
container=${MONGO_CONTAINER:-}
|
||||
if [[ -z $container ]]; then
|
||||
container=$(docker ps -q -f "label=com.docker.swarm.service.name=$MONGO_SERVICE" | head -n1)
|
||||
[[ -n $container ]] || container=$(docker ps -q -f "name=$MONGO_SERVICE" | head -n1)
|
||||
fi
|
||||
[[ -n $container ]] || die "MongoDB konteyneri bulunamadı"
|
||||
|
||||
drop=()
|
||||
exists=$(docker exec "$container" mongosh --quiet --eval \
|
||||
"db.adminCommand({listDatabases:1}).databases.some(d => d.name === '$target')")
|
||||
if [[ $exists == true ]]; then
|
||||
[[ $force == --force ]] || die "'$target' zaten var. Üzerine yazmak için --force (önce silinir)."
|
||||
drop=(--drop)
|
||||
fi
|
||||
|
||||
echo "[kakeibo-restore] $base → $target"
|
||||
docker exec -i "$container" mongorestore --archive --gzip --quiet "${drop[@]}" \
|
||||
--nsInclude="$from.*" --nsFrom="$from.*" --nsTo="$target.*" < "$src" \
|
||||
|| die "geri yükleme başarısız (yedek bozuk olabilir). '$target' yarım kalmış olabilir; kontrol edip silin."
|
||||
|
||||
counts() {
|
||||
docker exec "$container" mongosh --quiet "$1" --eval \
|
||||
'db.getCollectionNames().sort().map(c => c + " " + db[c].countDocuments({})).join("\n")'
|
||||
}
|
||||
echo "[kakeibo-restore] $target koleksiyonları (kayıt sayısı):"
|
||||
after=$(counts "$target"); echo "$after" | sed 's/^/ /'
|
||||
|
||||
if [[ -n ${COMPARE_DB:-} ]]; then
|
||||
if diff <(counts "$COMPARE_DB") <(echo "$after") >/dev/null; then
|
||||
echo "[kakeibo-restore] ✅ $COMPARE_DB ile aynı"
|
||||
else
|
||||
echo "[kakeibo-restore] ⚠️ $COMPARE_DB ile fark var (yedekten sonra yeni kayıt girildiyse normaldir):"
|
||||
diff <(counts "$COMPARE_DB") <(echo "$after") | sed 's/^/ /'
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
Reference in New Issue
Block a user