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:
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