feat: Faz 15 - canlıya alma (Dokploy, başlatma adımı, yedekleme, duman testi)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-07 02:13:17 +03:00
20 changed files with 727 additions and 22 deletions
+1
View File
@@ -2,6 +2,7 @@
node_modules node_modules
.next .next
out out
dist
coverage coverage
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
+1
View File
@@ -46,3 +46,4 @@ next-env.d.ts
# local tooling # local tooling
/scratch/ /scratch/
/dist
+1
View File
@@ -5,3 +5,4 @@ coverage
uploads uploads
next-env.d.ts next-env.d.ts
docs/PROMPT.md docs/PROMPT.md
dist/
+18
View File
@@ -6,6 +6,24 @@ Biçim [Keep a Changelog](https://keepachangelog.com/tr-TR/1.1.0/) esas alınır
## [Yayınlanmamış] ## [Yayınlanmamış]
## [1.0.0] - 2026-11-07
Faz 15: canlıya alma (Dokploy + Gitea, `butcem.ta1sph.website`).
### Eklendi
- Konteyner başlatma adımı (`docker-entrypoint.sh` → `scripts/bootstrap.mjs`): MongoDB replica set ve PRIMARY kontrolü (yoksa açık hata mesajıyla durur), bekleyen migration'lar, `SEED_ADMIN_*` tanımlıysa ilk admin hesabı (varsa dokunulmaz). esbuild ile tek dosyaya derlenir; imajda `tsx` gerekmez. Acil durumda `SKIP_BOOTSTRAP=1`.
- `/api/health` yanıtında `replicaSet` alanı (transaction desteğinin canlıda doğrulanması için).
- `docs/DEPLOY.md`: Dokploy uygulama kurulumu, ortam değişkenleri, volume, domain/HTTPS, Gitea webhook ile otomatik deploy, güncelleme/geri alma, yedekleme, geri yükleme ve sorun giderme; her komut açıklamalı.
- `docs/MONGODB-KURULUM.md`: mongo:7 servisinde replica set kontrolü, keyfile ile tek düğümlü replica set'e geçiş, uygulama kullanıcısı yetkileri, bağlantı dizesi, transaction denemesi.
- `scripts/smoke-test.sh`: sağlık (veritabanı, replica set, sürüm), güvenlik başlıkları, HTTP→HTTPS, oturumsuz erişim engeli ve isteğe bağlı giriş kontrolü; veri değiştirmez.
- `scripts/backup/`: günlük yedek betiği (mongodump + uploads arşivi, SHA-256 özetleri, 7 gün saklama, parola komut satırında görünmez), onaylı geri yükleme betiği ve ayar örneği.
### Değişti
- Docker imajı: `ENTRYPOINT` başlatma adımıyla; sağlık kontrolü başlangıç payı 60 sn.
- Uygulama mongo:7.0 ile uyumlu (tüm entegrasyon testleri mongo:7 replica set'e karşı geçti).
## [0.14.0] - 2026-11-07 ## [0.14.0] - 2026-11-07
Faz 14: dashboard ana grafiği ve raporlar (14a ve 14b alt fazları). Faz 14: dashboard ana grafiği ve raporlar (14a ve 14b alt fazları).
+8 -3
View File
@@ -2,6 +2,7 @@
# BÜTÇEM — çok aşamalı üretim imajı (Next.js standalone çıktı). # BÜTÇEM — çok aşamalı üretim imajı (Next.js standalone çıktı).
# Build: docker build --build-arg APP_COMMIT=$(git rev-parse --short HEAD) -t butcem . # Build: docker build --build-arg APP_COMMIT=$(git rev-parse --short HEAD) -t butcem .
# Çalıştır: docker run -p 3000:3000 --env-file .env.production -v butcem-uploads:/app/uploads butcem # Çalıştır: docker run -p 3000:3000 --env-file .env.production -v butcem-uploads:/app/uploads butcem
# Başlarken: replica set kontrolü, bekleyen migration'lar, ilk admin (docker-entrypoint.sh).
ARG NODE_VERSION=26 ARG NODE_VERSION=26
@@ -22,7 +23,8 @@ ENV NEXT_TELEMETRY_DISABLED=1 \
APP_COMMIT=${APP_COMMIT} APP_COMMIT=${APP_COMMIT}
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
RUN npm run build # Uygulama + başlatma betiği (migration ve ilk admin; tek dosya, tsx gerekmez).
RUN npm run build && npm run build:scripts
# 3) Çalışma zamanı — yalnızca standalone çıktı, root olmayan kullanıcı. # 3) Çalışma zamanı — yalnızca standalone çıktı, root olmayan kullanıcı.
FROM node:${NODE_VERSION}-alpine AS runner FROM node:${NODE_VERSION}-alpine AS runner
@@ -43,12 +45,15 @@ RUN addgroup -S -g 1001 nodejs && adduser -S -u 1001 -G nodejs nextjs \
COPY --from=builder --chown=nextjs:nodejs /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/dist/bootstrap.mjs ./scripts/bootstrap.mjs
COPY --chown=nextjs:nodejs --chmod=755 docker-entrypoint.sh ./docker-entrypoint.sh
USER nextjs USER nextjs
VOLUME ["/app/uploads"] VOLUME ["/app/uploads"]
EXPOSE 3000 EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ # start-period: ilk deploy'da migration'lar için pay.
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
CMD ["node", "server.js"] ENTRYPOINT ["/app/docker-entrypoint.sh"]
+24 -14
View File
@@ -52,19 +52,20 @@ Giriş kullanıcı adı **veya** e-posta ile yapılır. Kayıt kapalıdır; kull
## Scriptler ## Scriptler
| Komut | Açıklama | | Komut | Açıklama |
| ----------------------------- | --------------------------------- | | ----------------------------- | -------------------------------------------------------------------------------- |
| `npm run dev` | Geliştirme sunucusu | | `npm run dev` | Geliştirme sunucusu |
| `npm run build` / `start` | Üretim build'i / sunucusu | | `npm run build` / `start` | Üretim build'i / sunucusu |
| `npm run lint` | ESLint | | `npm run lint` | ESLint |
| `npm run typecheck` | `tsc --noEmit` | | `npm run typecheck` | `tsc --noEmit` |
| `npm run test` | Vitest birim testleri | | `npm run test` | Vitest birim testleri |
| `npm run format` | Prettier | | `npm run format` | Prettier |
| `npm run db:up` / `db:down` | Lokal MongoDB'yi başlat / durdur | | `npm run db:up` / `db:down` | Lokal MongoDB'yi başlat / durdur |
| `npm run seed` / `seed:reset` | Örnek veri (Faz 4) | | `npm run seed` / `seed:reset` | Örnek veri (Faz 4) |
| `npm run test:integration` | Entegrasyon testleri (MongoDB) | | `npm run test:integration` | Entegrasyon testleri (MongoDB) |
| `npm run migrate` | Bekleyen migration'lar | | `npm run migrate` | Bekleyen migration'lar |
| `npm run cleanup` | Eski/bağlanmamış ek dosyaları sil | | `npm run bootstrap` | Canlı başlatma adımı (replica set kontrolü + migration; production'da ilk admin) |
| `npm run cleanup` | Eski/bağlanmamış ek dosyaları sil |
## Haneler ve yetki ## Haneler ve yetki
@@ -83,4 +84,13 @@ docker build --build-arg APP_VERSION=$(node -p "require('./package.json').versio
--build-arg APP_COMMIT=$(git rev-parse --short HEAD) -t butcem . --build-arg APP_COMMIT=$(git rev-parse --short HEAD) -t butcem .
``` ```
Canlı ortam (Dokploy, `butcem.ta1sph.website`) kurulumu Faz 15'te `docs/DEPLOY.md` içinde anlatılacak. Konteyner başlarken `docker-entrypoint.sh` önce başlatma adımını (`scripts/bootstrap.mjs`: replica
set kontrolü, bekleyen migration'lar, `SEED_ADMIN_*` tanımlıysa ilk admin) sonra uygulamayı çalıştırır.
## Canlı ortam
- Kurulum, ortam değişkenleri, domain, volume, otomatik deploy, yedekleme ve geri yükleme:
[`docs/DEPLOY.md`](docs/DEPLOY.md)
- MongoDB replica set ve uygulama kullanıcısı: [`docs/MONGODB-KURULUM.md`](docs/MONGODB-KURULUM.md)
- Duman testi: `scripts/smoke-test.sh https://butcem.ta1sph.website`
- Yedek / geri yükleme betikleri: `scripts/backup/`
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
# BÜTÇEM konteyner girişi: önce başlatma (replica set kontrolü, migration'lar, ilk admin),
# başarılıysa uygulama. Başlatma başarısız olursa konteyner durur; Dokploy günlüğünde sebep görünür.
# Acil durumda atlamak için: SKIP_BOOTSTRAP=1
set -e
if [ "${SKIP_BOOTSTRAP:-0}" != "1" ]; then
node /app/scripts/bootstrap.mjs
fi
exec node /app/server.js
+254
View File
@@ -0,0 +1,254 @@
# Canlıya alma (Dokploy + Gitea)
BÜTÇEM'i Atlas sunucusunda Dokploy ile `https://butcem.ta1sph.website` adresinde yayınlamak,
güncellemek, yedeklemek ve geri yüklemek için adım adım kılavuz.
**Mimari:** Gitea (`main` dalı) → webhook → Dokploy, Dockerfile ile imaj build eder → konteyner
başlarken veritabanını hazırlar (replica set kontrolü, migration'lar, ilk admin) → Next.js
uygulaması 3000 portunda → Traefik HTTPS ile dışarı açar. MongoDB aynı Dokploy ağında, dışa portsuz.
Yüklenen dosyalar `/app/uploads` volume'unda.
> Sunucu komutları Atlas'ta çalıştırılır. Her komutun altında ne yaptığı yazıyor. `<…>` ile
> gösterilen yerleri kendi değerlerinizle değiştirin.
---
## 1. Ön koşullar
- [ ] MongoDB servisi **replica set** olarak çalışıyor ve `butcem_app` kullanıcısı hazır →
[`MONGODB-KURULUM.md`](MONGODB-KURULUM.md) (1–5. adımlar).
- [ ] Gitea'da BÜTÇEM reposu (`main` dalı güncel; `git push origin main`).
- [ ] DNS: `butcem.ta1sph.website` için Atlas'ın genel IP'sine **A kaydı** (ve gerekiyorsa
modemde 80/443 yönlendirmesi Traefik'e).
```bash
dig +short butcem.ta1sph.website
```
Alan adının çözüldüğü IP'yi gösterir; Atlas'ın genel IP'si olmalı.
## 2. Dokploy'da uygulama
Dokploy › proje › **Create Service › Application**, ad: `butcem`.
### 2.1 Kaynak (General)
- **Provider:** Gitea (Dokploy › Settings › Git'te Gitea bağlantısı tanımlıysa) ya da **Git**
(repo URL'si + Dokploy'un SSH anahtarı Gitea'da deploy key olarak ekli).
- **Repository / Branch:** BÜTÇEM reposu, `main`.
- **Build Type:** `Dockerfile` (yol: `Dockerfile`, context: `.`).
- **Build Args** (isteğe bağlı): `APP_COMMIT=<kısa-commit>` — sağlık kontrolünde görünür; boşsa
`unknown` yazar. Sürüm `package.json`'dan otomatik gelir.
### 2.2 Ortam değişkenleri (Environment)
| Değişken | Değer | Not |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `MONGODB_URI` | `mongodb://butcem_app:<parola>@<iç-host>:27017/butcem?authSource=<admin\|butcem>&replicaSet=rs0&directConnection=true` | Ayrıntı: MONGODB-KURULUM.md › 5 |
| `BETTER_AUTH_SECRET` | 32+ karakter rastgele | Aşağıdaki komutla üretin; **değiştirirseniz tüm oturumlar kapanır** |
| `BETTER_AUTH_URL` | `https://butcem.ta1sph.website` | Sonunda `/` yok |
| `SEED_ADMIN_EMAIL` | yönetici e-postası | İlk açılışta admin oluşturulur |
| `SEED_ADMIN_PASSWORD` | güçlü parola (8+) | İlk girişten sonra parolayı değiştirip bu değişkeni silebilirsiniz |
| `MAX_UPLOAD_MB` | `10` | İsteğe bağlı |
`NODE_ENV=production`, `PORT=3000` ve `UPLOAD_DIR=/app/uploads` imajda tanımlıdır; eklemeyin.
`SEED_USER_PASSWORD` canlıda kullanılmaz (örnek kullanıcı oluşturulmaz).
```bash
openssl rand -base64 32
```
`BETTER_AUTH_SECRET` için 32 baytlık rastgele bir değer üretir.
### 2.3 Volume (Advanced › Volumes / Mounts)
- **Volume Mount** · ad: `butcem-uploads` · bağlama yolu: **`/app/uploads`**.
Fiş, ürün görseli ve ek dosyalar burada tutulur; imaj yeniden oluşturulduğunda kaybolmaz.
(Bind mount yerine adlandırılmış volume kullanın; imajdaki dizin izinleri — uid 1001 — volume'a
ilk oluşturulurken aktarılır.)
### 2.4 Domain (Domains)
- **Host:** `butcem.ta1sph.website` · **Path:** `/` · **Container Port:** `3000`
- **HTTPS:** açık · **Certificate:** Let's Encrypt
### 2.5 İlk deploy
**Deploy**'a basın ve **Logs**'ta şu satırları bekleyin:
```
• MongoDB rs0 PRIMARY
✓ Migration: 001-initial-indexes — … (ilk kurulumda 15 migration)
✓ Admin oluşturuldu: <e-posta> (kullanıcı adı: admin)
✓ Ready in …
```
"MongoDB replica set olarak çalışmıyor" hatası görürseniz MONGODB-KURULUM.md › 3'ü uygulayın.
Konteynerin Docker sağlık durumu birkaç saniye içinde **healthy** olur (`/api/health`).
### 2.6 İlk giriş
`https://butcem.ta1sph.website` › kullanıcı adı **`admin`** ve `SEED_ADMIN_PASSWORD`.
1. **Yönetim › Kullanıcılar**'dan aile üyelerini oluşturun.
2. **Yönetim › Haneler**'den haneyi açıp üyeleri ve rollerini ekleyin.
3. (Önerilir) Admin parolasını **Yönetim › Kullanıcılar › kendi satırınız › Şifre sıfırla** ile
değiştirip Dokploy'da `SEED_ADMIN_PASSWORD`'u silin ve yeniden deploy edin; başlatma artık
yalnızca migration'ları çalıştırır. (Admin hesabı varken bu değişken parolayı değiştirmez.)
## 3. Duman testi (smoke test)
Kendi bilgisayarınızda, repo klasöründe:
```bash
scripts/smoke-test.sh https://butcem.ta1sph.website 1.0.0
```
Sağlık (veritabanı, replica set, sürüm), güvenlik başlıkları, HTTP→HTTPS yönlendirmesi ve
oturumsuz erişimin engellendiğini kontrol eder; **veri değiştirmez**. Tümü ✓ olmalı.
```bash
SMOKE_USER=admin SMOKE_PASSWORD='<parola>' scripts/smoke-test.sh https://butcem.ta1sph.website 1.0.0
```
Ayrıca giriş yapıp ana sayfanın açıldığını dener (parola komut geçmişine düşer; sonra
`history -d` ile silebilir ya da satırı boşlukla başlatabilirsiniz).
Elle kontrol listesi (bir kez, tarayıcıdan):
- [ ] Giriş / çıkış, telefondan PWA olarak ana ekrana ekleme.
- [ ] Bir hesap ve bir gider işlemi oluşturma (transaction'lı yazma).
- [ ] İşleme fiş fotoğrafı ekleyip görüntüleme (uploads volume).
- [ ] Bir transfer (iki hesap birden; transaction).
- [ ] Dokploy'da uygulamayı **Restart** edip fişin hâlâ açıldığını görme (volume kalıcılığı).
## 4. Otomatik deploy (Gitea webhook)
Dokploy › `butcem` › **Deployments** › **Webhook URL**'yi kopyalayın (Auto Deploy açık olmalı).
Gitea › repo › **Settings › Webhooks › Add Webhook › Gitea**:
- **Target URL:** kopyaladığınız Dokploy URL'si · **HTTP Method:** POST · **Content type:** JSON
- **Trigger On:** Push Events · **Branch filter:** `main`
- Kaydedip **Test Delivery** ile deneyin; Dokploy'da yeni bir deployment başlamalı.
Bundan sonra `git push origin main` → otomatik build ve yayın. Her deploy'da bekleyen
migration'lar uygulama açılmadan önce çalışır.
## 5. Güncelleme ve geri alma
- **Normal güncelleme:** `main`'e push (webhook) ya da Dokploy'da **Deploy**.
- **Büyük güncellemeden önce** elle yedek alın: `sudo /opt/butcem/butcem-backup.sh` (bkz. 6).
- **Geri alma:** Dokploy › Deployments › önceki başarılı deployment › **Rollback** (ya da
Gitea'da önceki commit'e dönüp push). Migration'lar yalnızca ileri yönlüdür; yeni sürümün
eklediği index/alanlar eski sürümü bozmaz. Veri bozulduysa 7'deki geri yüklemeyi kullanın.
- **Acil durum:** başlatma adımı (migration) hata verip uygulama açılmıyorsa geçici olarak
`SKIP_BOOTSTRAP=1` ortam değişkeniyle açabilirsiniz; sorunu giderince kaldırın.
## 6. Yedekleme
Günlük: `butcem` veritabanı (`mongodump`) + uploads volume arşivi + SHA-256 özetleri,
**7 gün saklama**, `/var/backups/butcem` altında. Betik: `scripts/backup/butcem-backup.sh`.
> ⚠️ Şimdilik yedekler **yalnızca sunucuda**. Disk arızasında yedekler de gider; bir sonraki adım
> olarak sunucu dışına kopya (NAS'a `rsync` ya da S3 uyumlu depolama) eklenmeli.
### 6.1 Kurulum (bir kez)
```bash
sudo install -d -m 755 /opt/butcem
```
Betikler için `/opt/butcem` klasörünü oluşturur.
```bash
sudo install -m 750 scripts/backup/butcem-backup.sh scripts/backup/butcem-restore.sh /opt/butcem/
```
Yedek ve geri yükleme betiklerini (repo klasöründen; sunucuya `scp` ile de kopyalayabilirsiniz)
`/opt/butcem`'e yalnızca root'un çalıştırabileceği izinle kopyalar.
```bash
sudo install -m 600 scripts/backup/butcem-backup.env.example /etc/butcem-backup.env
sudo nano /etc/butcem-backup.env
```
Ayar dosyasını yalnızca root'un okuyabileceği izinle oluşturur ve düzenlemek için açar. Doldurun:
- `MONGO_CONTAINER`: MongoDB konteyner adının ayırt edici kısmı (`docker ps --format '{{.Names}}'`).
- `MONGO_ROOT_USER` / `MONGO_ROOT_PASSWORD`: `butcem_root` bilgileri.
- `UPLOADS_VOLUME`: uygulamanın volume adı.
```bash
docker volume ls | grep -i uploads
```
Volume adlarını listeler (Dokploy'da verdiğiniz `butcem-uploads` adını doğrulayın).
### 6.2 İlk çalıştırma
```bash
sudo /opt/butcem/butcem-backup.sh
```
Yedeği hemen alır; sonunda `Tamam: … veritabanı, … dosya` yazmalı.
```bash
sudo ls -lh /var/backups/butcem && sudo sh -c 'cd /var/backups/butcem && sha256sum -c SHA256SUMS-*'
```
Yedek dosyalarını listeler ve bütünlüklerini doğrular (`OK`).
### 6.3 Günlük zamanlama
```bash
sudo crontab -e
```
root'un zamanlanmış görevlerini açar. Şu satırı ekleyin (her gece 03:30):
```
30 3 * * * /opt/butcem/butcem-backup.sh >> /var/log/butcem-backup.log 2>&1
```
```bash
sudo tail -n 20 /var/log/butcem-backup.log
```
Ertesi gün son yedeğin günlüğünü gösterir.
## 7. Geri yükleme
```bash
sudo ls -1 /var/backups/butcem
```
Mevcut yedekleri listeler; dönülecek tarih damgasını seçin.
1. Dokploy › `butcem` › **Stop** (yazma olmasın).
2. Geri yükleyin:
```bash
sudo /opt/butcem/butcem-restore.sh /var/backups/butcem/mongo-<damga>.archive.gz /var/backups/butcem/uploads-<damga>.tar.gz
```
Bütünlüğü kontrol eder, onay ister (`evet`), `butcem` veritabanını yedektekiyle **değiştirir**
ve uploads volume'unu arşivden yeniden yazar. Yalnızca veritabanı için ikinci dosyayı vermeyin.
3. Dokploy › **Start**, ardından duman testi (3).
**Tatbikat:** Ayda bir, en son yedeği bu adımlarla deneme amaçlı bir Dokploy kopyasına ya da
lokale (`docker compose` mongo'su) yükleyip açılıp açılmadığını kontrol edin. Yedeği hiç denenmemiş
bir yedek, yedek değildir.
## 8. Sorun giderme
| Belirti | Neden / çözüm |
| --------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| Logs: `MongoDB replica set olarak çalışmıyor` | MONGODB-KURULUM.md › 3 |
| Logs: `Authentication failed` | `MONGODB_URI` kullanıcı/parola/`authSource`; parola URL kodlu mu? |
| Logs: `Geçersiz ortam değişkenleri` | Listelenen değişkeni düzeltin (ör. `BETTER_AUTH_SECRET` < 32 karakter) |
| Konteyner `unhealthy` | `curl -s https://butcem.ta1sph.website/api/health` → `db` alanı; MongoDB servisi çalışıyor mu? |
| Giriş sonrası tekrar giriş sayfası | `BETTER_AUTH_URL` alan adıyla birebir aynı mı (https, sonda `/` yok)? |
| Yüklenen dosya açılmıyor / yükleme hatası | Volume `/app/uploads`'a bağlı mı? `docker exec <app> ls -ln /app/uploads` sahibi 1001 olmalı |
| Sertifika alınamadı | DNS A kaydı ve 80 portu Traefik'e ulaşıyor mu? |
```bash
docker logs --tail 100 $(docker ps -q --filter name=<butcem-uygulama-adı> | head -n1)
```
Uygulama konteynerinin son 100 log satırını gösterir (Dokploy › Logs ile aynı).
+144
View File
@@ -0,0 +1,144 @@
# MongoDB kurulumu (canlı, Dokploy)
BÜTÇEM çok adımlı yazmaları (transfer, "Alışverişi tamamla", kart ödemesi…) **MongoDB transaction**
ile yapar. Transaction yalnızca **replica set** üzerinde çalışır; tek sunucu için **tek düğümlü
replica set** (`rs0`) yeterlidir. Uygulama başlarken bunu kontrol eder; replica set yoksa açık bir
hata mesajıyla durur.
Bu belge mevcut kurulumu esas alır: Dokploy'da **`mongo:7`** veritabanı servisi (mongo:8 sunucu
çekirdeğiyle uyumsuz), kimlik doğrulama açık, `butcem_root` (yönetici) ve `butcem_app` (uygulama)
kullanıcıları oluşturulmuş. Uygulama mongo:7.0 ile test edilmiştir (tüm entegrasyon testleri).
> Komutlar Atlas sunucusunda çalıştırılır. `<…>` ile gösterilen yerleri kendi değerlerinizle
> değiştirin. Parolaları komut geçmişine yazmamak için `mongosh` parola sorduğunda girin.
---
## 1. MongoDB konteynerini bul
```bash
docker ps --format '{{.Names}}' | grep -i mongo
```
Çalışan konteynerlerin adlarından MongoDB'ninkini listeler. Dokploy ada rastgele bir son ek ekler
(ör. `butcem-mongo-a1b2c3.1.xyz`). Aşağıda bu adı `<MONGO>` olarak kullanın; adın ayırt edici
başlangıcı (ör. `butcem-mongo-a1b2c3`) da yeterlidir.
```bash
MONGO=$(docker ps -q --filter name=<mongo-servis-adı> | head -n1); echo "$MONGO"
```
Konteyner kimliğini bir değişkene alır; sonraki komutlar `$MONGO` ile kısalır.
## 2. Replica set açık mı?
```bash
docker exec -it "$MONGO" mongosh -u butcem_root --authenticationDatabase admin \
--eval 'try { const s = rs.status(); print(s.set, s.members[0].stateStr) } catch (e) { print("REPLICA SET YOK: " + e.codeName) }'
```
Yönetici olarak bağlanıp replica set durumunu yazdırır (parolayı sorar).
- Çıktı **`rs0 PRIMARY`** ise replica set hazır; **4. adıma** geçin.
- Çıktı **`REPLICA SET YOK: NoReplicationEnabled`** ise **3. adımı** uygulayın.
## 3. Tek düğümlü replica set'i aç
Dokploy'un MongoDB servisinde bir **"Replica Set"** seçeneği varsa (sürüme göre değişir) onu açıp
servisi yeniden deploy etmeniz ve 3.4'e geçmeniz yeterlidir. Yoksa aşağıdaki elle yöntemi uygulayın.
Kimlik doğrulama açıkken replica set bir **anahtar dosyası (keyfile)** ister.
### 3.1 Yedek alın
```bash
docker exec -it "$MONGO" mongodump -u butcem_root --authenticationDatabase admin --gzip --archive=/data/db/oncesi.archive.gz
docker cp "$MONGO":/data/db/oncesi.archive.gz ~/mongo-oncesi.archive.gz && docker exec "$MONGO" rm /data/db/oncesi.archive.gz
```
Değişiklikten önce tüm veritabanlarının yedeğini konteyner içinde bir dosyaya alır (parolayı sorar),
sonra ev dizinine kopyalayıp konteynerdeki kopyayı siler. Yeni kurulumda veri yoksa atlanabilir.
### 3.2 Anahtar dosyasını oluştur
```bash
docker exec "$MONGO" sh -c 'head -c 756 /dev/urandom | base64 > /data/db/rs-keyfile && chmod 400 /data/db/rs-keyfile && chown 999:999 /data/db/rs-keyfile && ls -l /data/db/rs-keyfile'
```
Kalıcı veri volume'u (`/data/db`) içinde rastgele bir anahtar dosyası üretir; yalnızca `mongodb`
kullanıcısı (uid 999) okuyabilir. Çıktıda `-r-------- … mongodb mongodb` görmelisiniz. Dosya
volume'da olduğu için yeniden başlatmalarda korunur.
### 3.3 Başlatma komutunu değiştir ve yeniden deploy et
Dokploy › MongoDB servisi › **Advanced** sekmesi › **Command** alanına:
```
mongod --replSet rs0 --bind_ip_all --keyFile /data/db/rs-keyfile
```
yazıp kaydedin ve servisi **Redeploy** edin. (`MONGO_INITDB_ROOT_*` ortam değişkenleri yerinde
kaldığı için kimlik doğrulama açık kalır.) Konteyner yeniden oluşacağından 1. adımdaki `MONGO`
değişkenini yeniden alın.
### 3.4 Replica set'i başlat (tek seferlik)
```bash
docker exec -it "$MONGO" mongosh -u butcem_root --authenticationDatabase admin \
--eval 'rs.initiate({ _id: "rs0", members: [{ _id: 0, host: "localhost:27017" }] })'
```
`rs0` adlı tek üyeli replica set'i başlatır; çıktıda `{ ok: 1 }` görmelisiniz. Uygulama
`directConnection=true` ile bağlandığı için üye adresinin `localhost` olması sorun değildir.
Birkaç saniye sonra 2. adımdaki kontrolü tekrarlayın: **`rs0 PRIMARY`**.
## 4. Uygulama kullanıcısını doğrula
```bash
docker exec -it "$MONGO" mongosh -u butcem_root --authenticationDatabase admin --eval '
for (const d of ["admin", "butcem"]) {
const u = db.getSiblingDB(d).getUser("butcem_app");
if (u) print("tanımlı veritabanı:", d, "| roller:", JSON.stringify(u.roles));
}'
```
`butcem_app` kullanıcısının hangi veritabanında tanımlı olduğunu (bağlantı dizesindeki
`authSource`) ve rollerini yazdırır. Beklenen rol: `[{"role":"readWrite","db":"butcem"}]`.
Rol farklıysa düzeltin:
```bash
docker exec -it "$MONGO" mongosh -u butcem_root --authenticationDatabase admin \
--eval 'db.getSiblingDB("<authSource>").updateUser("butcem_app", { roles: [{ role: "readWrite", db: "butcem" }] })'
```
Uygulama kullanıcısına yalnızca `butcem` veritabanında okuma-yazma yetkisi verir (migration'lar
index oluşturmak için bu yetkiyle yetinir).
## 5. Bağlantı dizesi (Dokploy uygulama ortam değişkeni)
```
MONGODB_URI=mongodb://butcem_app:<parola-url-kodlu>@<iç-host>:27017/butcem?authSource=<authSource>&replicaSet=rs0&directConnection=true
```
- `<iç-host>`: Dokploy › MongoDB servisi › **Internal Host** (ör. `butcem-mongo-a1b2c3`). Uygulama
ve veritabanı aynı Dokploy ağında olduğundan **dışa port açmaya gerek yoktur**; açmayın.
- `<authSource>`: 4. adımın çıktısındaki veritabanı (`admin` veya `butcem`).
- Parolada `@ : / ? # %` gibi karakterler varsa URL kodlayın:
```bash
python3 -c 'import urllib.parse,getpass; print(urllib.parse.quote(getpass.getpass(), safe=""))'
```
Parolayı sorar ve bağlantı dizesine yazılacak kodlu hâlini basar (geçmişe düşmez).
## 6. Transaction denemesi (isteğe bağlı)
```bash
docker exec -it "$MONGO" mongosh "mongodb://butcem_app@localhost:27017/butcem?authSource=<authSource>&replicaSet=rs0&directConnection=true" --eval '
const s = db.getMongo().startSession(); const c = s.getDatabase("butcem").getCollection("tx_probe");
s.startTransaction(); c.insertOne({ a: 1 }); s.commitTransaction();
print("transaction tamam:", db.getSiblingDB("butcem").getCollection("tx_probe").countDocuments({}));
db.getSiblingDB("butcem").getCollection("tx_probe").drop();'
```
Uygulama kullanıcısıyla bağlanıp küçük bir transaction yazar, sonucu basar ve deneme
koleksiyonunu siler. `transaction tamam: 1` görmelisiniz.
+1
View File
@@ -326,6 +326,7 @@ Aşağıdakileri varsayılan olarak kabul ettim. Faz 0'da yalnızca bunlardan em
- **Faz 12 kararları (2026-11-06):** 12a mağazalar ve ürünler (marka/paket, görseller), 12b fiyat kayıtları, birim fiyat ve fiyat geçmişi; `v0.12.0` 12b sonunda. Mağazalar, ürünler ve fiyatlar hane bazlıdır (hane içinde ortak, başka hane görmez). Paket birimi ürün tipinin birimine kendiliğinden çevrilemiyorsa ürün kaydında dönüşüm zorunludur ("5 kg = 33 yıkama"); her fiyat ürün tipinin biriminde karşılaştırılır. - **Faz 12 kararları (2026-11-06):** 12a mağazalar ve ürünler (marka/paket, görseller), 12b fiyat kayıtları, birim fiyat ve fiyat geçmişi; `v0.12.0` 12b sonunda. Mağazalar, ürünler ve fiyatlar hane bazlıdır (hane içinde ortak, başka hane görmez). Paket birimi ürün tipinin birimine kendiliğinden çevrilemiyorsa ürün kaydında dönüşüm zorunludur ("5 kg = 33 yıkama"); her fiyat ürün tipinin biriminde karşılaştırılır.
- **Faz 13 kararları (2026-11-06):** 13a alışveriş listeleri, stok eşiği önerileri (onayla/reddet), akıllı mağaza önerisi ve alışveriş modu; 13b alışveriş sonrası giriş, karşılaştırma raporu ve "Alışverişi tamamla"; `v0.13.0` 13b sonunda. Birden çok mağazadan alışverişte mağaza başına ayrı bölünmüş gider (her mağaza için ayrı hesap ve fiş). Tamamlanınca liste kapanır ve rapor sabitlenir; alınmayanlar başka aktif listeye taşınır, yeni listeye alınır ya da bırakılır. Reddedilen öneri yeni alım girilene kadar gösterilmez. Markası belirtilmeyen ürün tipi kalemi (ör. pazardan domates) tipin biriminde stoğa işlenir, fiyat kaydı oluşmaz. - **Faz 13 kararları (2026-11-06):** 13a alışveriş listeleri, stok eşiği önerileri (onayla/reddet), akıllı mağaza önerisi ve alışveriş modu; 13b alışveriş sonrası giriş, karşılaştırma raporu ve "Alışverişi tamamla"; `v0.13.0` 13b sonunda. Birden çok mağazadan alışverişte mağaza başına ayrı bölünmüş gider (her mağaza için ayrı hesap ve fiş). Tamamlanınca liste kapanır ve rapor sabitlenir; alınmayanlar başka aktif listeye taşınır, yeni listeye alınır ya da bırakılır. Reddedilen öneri yeni alım girilene kadar gösterilmez. Markası belirtilmeyen ürün tipi kalemi (ör. pazardan domates) tipin biriminde stoğa işlenir, fiyat kaydı oluşmaz.
- **Faz 14 kararları (2026-11-07):** 14a dashboard ana grafiği (Haftalık/Aylık/Altı aylık/Yıllık, kümülatif/dönemsel) ve finans raporları (kategori dağılımı, aylık trend, kişi ve hesap bazlı, bütçe vs. gerçekleşen); 14b cari ve aynî borçlar, birikim kâr/zarar, alışveriş (liste vs. gerçekleşen, mağaza bazlı harcama), birim fiyat geçmişi, fire/zarar; `v0.14.0` 14b sonunda. Raporlar `/raporlar` altında ayrı sayfalar; dönem ve filtreler URL'de, her raporda CSV. "Ay" bütçe ayıdır; gider kuralları bütçeyle aynı (taksit modu, gizli hesap ayarı). Kişi bazlı harcama gideri kaydeden üyeye göre; mağaza bazlı harcama gider alıcısının mağaza adıyla başlamasına göre (şubeler birlikte). - **Faz 14 kararları (2026-11-07):** 14a dashboard ana grafiği (Haftalık/Aylık/Altı aylık/Yıllık, kümülatif/dönemsel) ve finans raporları (kategori dağılımı, aylık trend, kişi ve hesap bazlı, bütçe vs. gerçekleşen); 14b cari ve aynî borçlar, birikim kâr/zarar, alışveriş (liste vs. gerçekleşen, mağaza bazlı harcama), birim fiyat geçmişi, fire/zarar; `v0.14.0` 14b sonunda. Raporlar `/raporlar` altında ayrı sayfalar; dönem ve filtreler URL'de, her raporda CSV. "Ay" bütçe ayıdır; gider kuralları bütçeyle aynı (taksit modu, gizli hesap ayarı). Kişi bazlı harcama gideri kaydeden üyeye göre; mağaza bazlı harcama gider alıcısının mağaza adıyla başlamasına göre (şubeler birlikte).
- **Faz 15 kararları (2026-11-07):** Canlı MongoDB Dokploy servisi **mongo:7** (mongo:8 sunucu çekirdeğiyle uyumsuz); uygulama mongo:7.0'a karşı tüm entegrasyon testleriyle doğrulandı. Replica set kimlik doğrulamalı tek düğüm (`rs0`, keyfile), uygulama `directConnection=true` ile bağlanır (`docs/MONGODB-KURULUM.md`). Migration'lar ve ilk admin konteyner başlarken otomatik (`scripts/bootstrap.mjs`; replica set yoksa uygulama açılmaz). Gitea `main` push → Dokploy webhook ile otomatik deploy. Yedek: günlük `mongodump` + uploads arşivi, 7 gün, şimdilik yalnızca sunucuda (sunucu dışı kopya sonraki adım).
- **Lokal MongoDB portu:** host'ta `27019` (27017 ve 27018 geliştirme makinesinde dolu). `BUTCEM_MONGO_PORT` ile değiştirilebilir. - **Lokal MongoDB portu:** host'ta `27019` (27017 ve 27018 geliştirme makinesinde dolu). `BUTCEM_MONGO_PORT` ile değiştirilebilir.
--- ---
+2
View File
@@ -20,6 +20,8 @@ const eslintConfig = defineConfig([
".next/**", ".next/**",
"out/**", "out/**",
"build/**", "build/**",
// Derlenmiş başlatma betiği (esbuild paketi).
"dist/**",
"next-env.d.ts", "next-env.d.ts",
]), ]),
]); ]);
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "butcem", "name": "butcem",
"version": "0.14.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "butcem", "name": "butcem",
"version": "0.14.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@hookform/resolvers": "^7.0.2", "@hookform/resolvers": "^7.0.2",
"@tanstack/react-table": "^9.3.1", "@tanstack/react-table": "^9.3.1",
+3 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "butcem", "name": "butcem",
"version": "0.14.0", "version": "1.0.0",
"private": true, "private": true,
"description": "BÜTÇEM — aile bütçe, cari, birikim, ev stoğu ve akıllı alışveriş uygulaması", "description": "BÜTÇEM — aile bütçe, cari, birikim, ev stoğu ve akıllı alışveriş uygulaması",
"engines": { "engines": {
@@ -23,6 +23,8 @@
"icons": "tsx scripts/generate-icons.ts", "icons": "tsx scripts/generate-icons.ts",
"test:integration": "vitest run --config vitest.integration.config.mts", "test:integration": "vitest run --config vitest.integration.config.mts",
"migrate": "tsx --conditions=react-server --env-file=.env.local scripts/migrate.ts", "migrate": "tsx --conditions=react-server --env-file=.env.local scripts/migrate.ts",
"bootstrap": "tsx --conditions=react-server --env-file=.env.local scripts/bootstrap.ts",
"build:scripts": "esbuild scripts/bootstrap.ts --bundle --platform=node --format=esm --target=node22 --conditions=react-server --outfile=dist/bootstrap.mjs --external:sharp --external:kerberos --external:snappy --external:socks --external:aws4 --external:@mongodb-js/zstd --external:mongodb-client-encryption --external:gcp-metadata --external:@aws-sdk/credential-providers --banner:js=\"import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);\" --log-level=warning",
"cleanup": "tsx --conditions=react-server --env-file=.env.local scripts/cleanup.ts" "cleanup": "tsx --conditions=react-server --env-file=.env.local scripts/cleanup.ts"
}, },
"dependencies": { "dependencies": {
+13
View File
@@ -0,0 +1,13 @@
# /etc/butcem-backup.env — butcem-backup.sh ayarları. İzin: sudo chmod 600 /etc/butcem-backup.env
# Değerler için docs/DEPLOY.md › Yedekleme.
# MongoDB konteyner adı veya adının ayırt edici bir parçası (docker ps --format '{{.Names}}')
MONGO_CONTAINER="butcem-mongo"
MONGO_ROOT_USER="butcem_root"
MONGO_ROOT_PASSWORD=""
# Uygulamanın /app/uploads volume adı (docker volume ls)
UPLOADS_VOLUME="butcem-uploads"
BACKUP_DIR="/var/backups/butcem"
RETENTION_DAYS="7"
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# BÜTÇEM günlük yedek — sunucuda (Atlas) root olarak, cron ile çalışır. Ayrıntı: docs/DEPLOY.md › Yedekleme
#
# Üretir (BACKUP_DIR altında, tarih damgalı):
# mongo-YYYYmmdd-HHMMSS.archive.gz → butcem veritabanı (mongodump --archive --gzip)
# uploads-YYYYmmdd-HHMMSS.tar.gz → yüklenen dosyalar (uploads volume)
# SHA256SUMS-YYYYmmdd-HHMMSS → bütünlük özetleri
# RETENTION_DAYS günden eski yedekleri siler.
#
# Ayarlar /etc/butcem-backup.env dosyasından okunur (yalnızca root okuyabilmeli: chmod 600).
set -euo pipefail
CONFIG="${BUTCEM_BACKUP_CONFIG:-/etc/butcem-backup.env}"
# shellcheck source=/dev/null
[ -f "$CONFIG" ] && . "$CONFIG"
: "${MONGO_CONTAINER:?MONGO_CONTAINER tanımlı değil (MongoDB konteyner adı veya adının bir parçası)}"
: "${MONGO_ROOT_USER:?MONGO_ROOT_USER tanımlı değil}"
: "${MONGO_ROOT_PASSWORD:?MONGO_ROOT_PASSWORD tanımlı değil}"
: "${UPLOADS_VOLUME:?UPLOADS_VOLUME tanımlı değil (uygulamanın /app/uploads volume adı)}"
BACKUP_DIR="${BACKUP_DIR:-/var/backups/butcem}"
RETENTION_DAYS="${RETENTION_DAYS:-7}"
DB_NAME="${DB_NAME:-butcem}"
HELPER_IMAGE="${HELPER_IMAGE:-alpine:3.20}"
TS="$(date +%Y%m%d-%H%M%S)"
log() { echo "[$(date '+%F %T')] $*"; }
# Dokploy konteyner adlarına rastgele son ek ekler; adın bir parçasıyla eşleşen çalışan konteyner.
CID="$(docker ps -q --filter "name=${MONGO_CONTAINER}" | head -n1)"
[ -n "$CID" ] || { log "HATA: '${MONGO_CONTAINER}' adlı çalışan MongoDB konteyneri yok."; exit 1; }
docker volume inspect "$UPLOADS_VOLUME" >/dev/null 2>&1 || { log "HATA: '${UPLOADS_VOLUME}' volume'u yok."; exit 1; }
install -d -m 700 "$BACKUP_DIR"
MONGO_FILE="$BACKUP_DIR/mongo-$TS.archive.gz"
UPLOADS_FILE="$BACKUP_DIR/uploads-$TS.tar.gz"
# 1) MongoDB: parola komut satırında görünmesin diye geçici yapılandırma dosyasıyla.
log "MongoDB yedeği alınıyor ($DB_NAME)…"
CONF="/tmp/butcem-dump-$TS.yaml"
printf 'password: "%s"\n' "$MONGO_ROOT_PASSWORD" | docker exec -i "$CID" sh -c "umask 077 && cat > $CONF"
if ! docker exec "$CID" mongodump --quiet --config "$CONF" --username "$MONGO_ROOT_USER" \
--authenticationDatabase admin --db "$DB_NAME" --archive --gzip >"$MONGO_FILE"; then
docker exec "$CID" rm -f "$CONF"
rm -f "$MONGO_FILE"
log "HATA: mongodump başarısız."
exit 1
fi
docker exec "$CID" rm -f "$CONF"
[ -s "$MONGO_FILE" ] || { log "HATA: MongoDB yedeği boş."; exit 1; }
# 2) Yüklenen dosyalar: volume salt okunur bağlanır.
log "Uploads yedeği alınıyor ($UPLOADS_VOLUME)…"
docker run --rm -v "$UPLOADS_VOLUME":/data:ro -v "$BACKUP_DIR":/backup "$HELPER_IMAGE" \
tar czf "/backup/$(basename "$UPLOADS_FILE")" -C /data .
[ -s "$UPLOADS_FILE" ] || { log "HATA: uploads yedeği boş."; exit 1; }
# 3) Bütünlük özeti ve izinler
( cd "$BACKUP_DIR" && sha256sum "$(basename "$MONGO_FILE")" "$(basename "$UPLOADS_FILE")" >"SHA256SUMS-$TS" )
chmod 600 "$MONGO_FILE" "$UPLOADS_FILE" "$BACKUP_DIR/SHA256SUMS-$TS"
# 4) Saklama süresi
find "$BACKUP_DIR" -maxdepth 1 -type f \( -name 'mongo-*.archive.gz' -o -name 'uploads-*.tar.gz' -o -name 'SHA256SUMS-*' \) \
-mtime +"$RETENTION_DAYS" -print -delete | sed 's/^/ silindi: /'
log "Tamam: $(du -h "$MONGO_FILE" | cut -f1) veritabanı, $(du -h "$UPLOADS_FILE" | cut -f1) dosya → $BACKUP_DIR"
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# BÜTÇEM geri yükleme — sunucuda root olarak, ELLE çalıştırılır. Ayrıntı: docs/DEPLOY.md › Geri yükleme
#
# sudo scripts/backup/butcem-restore.sh /var/backups/butcem/mongo-….archive.gz [/var/backups/butcem/uploads-….tar.gz]
#
# DİKKAT: butcem veritabanındaki koleksiyonlar yedektekilerle DEĞİŞTİRİLİR (--drop); uploads arşivi
# verilirse volume içeriği silinip arşivden yazılır. Önce uygulamayı durdurun (Dokploy › Stop).
# Ayarlar butcem-backup.sh ile aynı dosyadan: /etc/butcem-backup.env
set -euo pipefail
CONFIG="${BUTCEM_BACKUP_CONFIG:-/etc/butcem-backup.env}"
# shellcheck source=/dev/null
[ -f "$CONFIG" ] && . "$CONFIG"
: "${MONGO_CONTAINER:?}" "${MONGO_ROOT_USER:?}" "${MONGO_ROOT_PASSWORD:?}" "${UPLOADS_VOLUME:?}"
DB_NAME="${DB_NAME:-butcem}"
HELPER_IMAGE="${HELPER_IMAGE:-alpine:3.20}"
MONGO_FILE="${1:?Kullanım: butcem-restore.sh <mongo-arşivi> [uploads-arşivi]}"
UPLOADS_FILE="${2:-}"
[ -s "$MONGO_FILE" ] || { echo "Arşiv yok veya boş: $MONGO_FILE"; exit 1; }
[ -z "$UPLOADS_FILE" ] || [ -s "$UPLOADS_FILE" ] || { echo "Arşiv yok veya boş: $UPLOADS_FILE"; exit 1; }
# Bütünlük kontrolü (aynı klasörde SHA256SUMS varsa)
DIR="$(cd "$(dirname "$MONGO_FILE")" && pwd)"
STAMP="$(basename "$MONGO_FILE" | sed -E 's/^mongo-(.*)\.archive\.gz$/\1/')"
if [ -f "$DIR/SHA256SUMS-$STAMP" ]; then
(cd "$DIR" && sha256sum -c "SHA256SUMS-$STAMP") || { echo "Bütünlük kontrolü başarısız."; exit 1; }
fi
CID="$(docker ps -q --filter "name=${MONGO_CONTAINER}" | head -n1)"
[ -n "$CID" ] || { echo "Çalışan MongoDB konteyneri yok: $MONGO_CONTAINER"; exit 1; }
echo "Geri yüklenecek: $MONGO_FILE${UPLOADS_FILE:+ ve $UPLOADS_FILE}"
echo "'$DB_NAME' veritabanındaki mevcut veri yedektekiyle DEĞİŞTİRİLECEK."
if [ "${ASSUME_YES:-0}" != "1" ]; then
read -r -p "Devam etmek için 'evet' yazın: " ANSWER
[ "$ANSWER" = "evet" ] || { echo "Vazgeçildi."; exit 1; }
fi
CONF="/tmp/butcem-restore-$$.yaml"
printf 'password: "%s"\n' "$MONGO_ROOT_PASSWORD" | docker exec -i "$CID" sh -c "umask 077 && cat > $CONF"
trap 'docker exec "$CID" rm -f "$CONF" >/dev/null 2>&1 || true' EXIT
docker exec -i "$CID" mongorestore --quiet --config "$CONF" --username "$MONGO_ROOT_USER" \
--authenticationDatabase admin --archive --gzip --drop --nsInclude "${DB_NAME}.*" <"$MONGO_FILE"
echo "✓ Veritabanı geri yüklendi."
if [ -n "$UPLOADS_FILE" ]; then
docker run --rm -v "$UPLOADS_VOLUME":/data -v "$(cd "$(dirname "$UPLOADS_FILE")" && pwd)":/backup:ro "$HELPER_IMAGE" \
sh -c "find /data -mindepth 1 -delete && tar xzf /backup/$(basename "$UPLOADS_FILE") -C /data && chown -R 1001:1001 /data"
echo "✓ Yüklenen dosyalar geri yüklendi."
fi
echo "Uygulamayı yeniden başlatın (Dokploy › Start) ve duman testini çalıştırın."
+43
View File
@@ -0,0 +1,43 @@
/**
* Canlı başlatma (Docker entrypoint, uygulamadan önce): replica set kontrolü, bekleyen migration'lar
* ve SEED_ADMIN_* tanımlıysa ilk admin hesabı (varsa dokunulmaz). İdempotent; her deploy'da çalışır.
* İmajda esbuild ile tek dosyaya derlenir (scripts/bootstrap.mjs). Lokal: npm run bootstrap
*/
import mongoose from "mongoose";
import { getDb, getMongoClient } from "@/lib/db/mongo";
import { env } from "@/lib/env";
import { runMigrations } from "./migrations";
import { closeSeedConnections, runSeed } from "./seed/run";
async function main() {
const e = env();
const hello = await getDb().admin().command({ hello: 1 });
if (!hello.setName) {
throw new Error(
"MongoDB replica set olarak çalışmıyor; transaction'lı yazmalar başarısız olur. " +
"docs/DEPLOY.md › 'MongoDB replica set' adımlarını uygulayın.",
);
}
if (!hello.isWritablePrimary) throw new Error("MongoDB düğümü PRIMARY değil (yazılamaz).");
console.log(`• MongoDB ${hello.setName} PRIMARY`);
if (e.NODE_ENV === "production" && e.SEED_ADMIN_EMAIL && e.SEED_ADMIN_PASSWORD) {
// Production'da seed yalnızca migration + admin yapar (örnek veri oluşturulmaz).
await runSeed();
} else {
await runMigrations();
if (e.NODE_ENV === "production")
console.log("• SEED_ADMIN_* tanımlı değil; admin kontrolü atlandı.");
}
}
main()
.catch((err: unknown) => {
console.error(`✗ Başlatma hatası: ${err instanceof Error ? err.message : err}`);
process.exitCode = 1;
})
.finally(async () => {
await closeSeedConnections();
await mongoose.disconnect();
await getMongoClient().close();
});
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# BÜTÇEM duman testi (smoke test) — canlıya alma sonrası, yalnızca OKUMA yapar; veri değiştirmez.
#
# Kullanım:
# scripts/smoke-test.sh https://butcem.ta1sph.website [beklenen-sürüm]
# SMOKE_USER=admin SMOKE_PASSWORD='…' scripts/smoke-test.sh https://butcem.ta1sph.website 1.0.0
#
# Gerekenler: bash, curl. Çıkış kodu: 0 = hepsi geçti, 1 = en az bir kontrol başarısız.
set -uo pipefail
BASE="${1:?Kullanım: scripts/smoke-test.sh <adres> [beklenen-sürüm]}"
BASE="${BASE%/}"
EXPECT_VERSION="${2:-}"
FAIL=0
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; }
bad() { printf ' \033[31m✗\033[0m %s\n' "$1"; FAIL=1; }
check() { if eval "$2"; then ok "$1"; else bad "$1"; fi; }
echo "BÜTÇEM duman testi: $BASE"
echo "1) Sağlık kontrolü"
HEALTH_CODE=$(curl -s -o "$TMP/health" -w '%{http_code}' "$BASE/api/health")
HEALTH=$(cat "$TMP/health")
echo " $HEALTH"
check "GET /api/health 200" '[ "$HEALTH_CODE" = 200 ]'
check "veritabanı erişilebilir" 'grep -q "\"db\":\"ok\"" <<<"$HEALTH"'
check "MongoDB replica set (transaction için)" '! grep -q "\"replicaSet\":null" <<<"$HEALTH" && grep -q "\"replicaSet\":\"" <<<"$HEALTH"'
if [ -n "$EXPECT_VERSION" ]; then
check "sürüm $EXPECT_VERSION" 'grep -q "\"version\":\"$EXPECT_VERSION\"" <<<"$HEALTH"'
fi
echo "2) Sayfalar ve güvenlik başlıkları"
LOGIN_CODE=$(curl -s -D "$TMP/login-headers" -o /dev/null -w '%{http_code}' "$BASE/login")
H=$(tr -d '\r' <"$TMP/login-headers" | tr 'A-Z' 'a-z')
check "GET /login 200" '[ "$LOGIN_CODE" = 200 ]'
check "Content-Security-Policy var" 'grep -q "^content-security-policy:" <<<"$H"'
check "X-Frame-Options: DENY" 'grep -q "^x-frame-options: deny" <<<"$H"'
check "X-Content-Type-Options: nosniff" 'grep -q "^x-content-type-options: nosniff" <<<"$H"'
if [[ "$BASE" == https://* ]]; then
check "Strict-Transport-Security var" 'grep -q "^strict-transport-security:" <<<"$H"'
HTTP_URL="http://${BASE#https://}/login"
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' "$HTTP_URL")
check "HTTP → HTTPS yönlendirmesi ($HTTP_CODE)" '[[ "$HTTP_CODE" =~ ^30[1278]$ ]]'
fi
echo "3) Oturumsuz erişim engelleniyor"
DASH=$(curl -s -o /dev/null -w '%{http_code} %{redirect_url}' "$BASE/dashboard")
check "/dashboard → giriş sayfasına yönlendirme ($DASH)" '[[ "$DASH" =~ ^30[27]\ .*/login ]]'
FILE_CODE=$(curl -s -o /dev/null -w '%{http_code}' "$BASE/api/files/000000000000000000000000")
check "/api/files oturumsuz 401 ($FILE_CODE)" '[ "$FILE_CODE" = 401 ]'
CSV=$(curl -s -o "$TMP/csv" -w '%{http_code}' "$BASE/raporlar/kategoriler/csv")
check "rapor CSV'si oturumsuz veri vermiyor ($CSV)" '! grep -q "Toplam gidere oran" "$TMP/csv"'
if [ -n "${SMOKE_USER:-}" ] && [ -n "${SMOKE_PASSWORD:-}" ]; then
echo "4) Giriş ($SMOKE_USER)"
SIGN=$(curl -s -c "$TMP/jar" -o "$TMP/signin" -w '%{http_code}' -X POST "$BASE/api/auth/sign-in/username" \
-H 'Content-Type: application/json' -H "Origin: $BASE" \
--data "$(printf '{"username":"%s","password":"%s"}' "$SMOKE_USER" "$SMOKE_PASSWORD")")
check "giriş 200 ($SIGN)" '[ "$SIGN" = 200 ]'
PAGE=$(curl -s -b "$TMP/jar" -L -o /dev/null -w '%{http_code} %{url_effective}' "$BASE/dashboard")
check "oturumla ana sayfa açılıyor ($PAGE)" '[[ "$PAGE" =~ ^200\ ]] && [[ ! "$PAGE" =~ /login ]]'
curl -s -b "$TMP/jar" -X POST "$BASE/api/auth/sign-out" -H "Origin: $BASE" -o /dev/null
else
echo "4) Giriş testi atlandı (SMOKE_USER ve SMOKE_PASSWORD tanımlı değil)"
fi
echo
if [ "$FAIL" = 0 ]; then echo "Sonuç: tüm kontroller geçti."; else echo "Sonuç: başarısız kontrol var."; fi
exit "$FAIL"
+12 -1
View File
@@ -1,5 +1,5 @@
import { connection } from "next/server"; import { connection } from "next/server";
import { pingDb } from "@/lib/db/mongo"; import { getDb, pingDb } from "@/lib/db/mongo";
import { APP_COMMIT, APP_VERSION } from "@/lib/version"; import { APP_COMMIT, APP_VERSION } from "@/lib/version";
/** /**
@@ -10,10 +10,21 @@ export async function GET() {
// Cache Components açıkken GET handler'ı build'de prerender edilmesin. // Cache Components açıkken GET handler'ı build'de prerender edilmesin.
await connection(); await connection();
const dbOk = await pingDb(); const dbOk = await pingDb();
// Transaction'lar için replica set şart; adı yoksa (standalone) null.
let replicaSet: string | null = null;
if (dbOk) {
try {
const hello = await getDb().admin().command({ hello: 1 }, { timeoutMS: 2_000 });
replicaSet = typeof hello.setName === "string" ? hello.setName : null;
} catch {
replicaSet = null;
}
}
return Response.json( return Response.json(
{ {
status: dbOk ? "ok" : "degraded", status: dbOk ? "ok" : "degraded",
db: dbOk ? "ok" : "unreachable", db: dbOk ? "ok" : "unreachable",
replicaSet,
version: APP_VERSION, version: APP_VERSION,
commit: APP_COMMIT, commit: APP_COMMIT,
time: new Date().toISOString(), time: new Date().toISOString(),
+1 -1
View File
@@ -28,7 +28,7 @@ export type NavItem = {
/** Şu an tamamlanmış en yüksek faz — sayfalar eklendikçe artar. */ /** Şu an tamamlanmış en yüksek faz — sayfalar eklendikçe artar. */
/** Alt fazlar ondalıkla: 6.1 = Faz 6a. */ /** Alt fazlar ondalıkla: 6.1 = Faz 6a. */
export const CURRENT_PHASE = 14.2; export const CURRENT_PHASE = 15;
export const isAvailable = (item: NavItem) => item.phase <= CURRENT_PHASE; export const isAvailable = (item: NavItem) => item.phase <= CURRENT_PHASE;