diff --git a/src/app/api/backups/test/route.ts b/src/app/api/backups/test/route.ts
index 5227b8a..90d00d0 100644
--- a/src/app/api/backups/test/route.ts
+++ b/src/app/api/backups/test/route.ts
@@ -22,7 +22,7 @@ export async function POST(req: NextRequest) {
const db = config.databases.find(d => d.id === db_id)
if (!db) return NextResponse.json({ ok: false, error: 'Veritabanı bulunamadı.' })
- const filename = backupFilename()
+ const filename = backupFilename(db.name)
try {
// 1. Dump al
diff --git a/src/app/dashboard/databases/page.tsx b/src/app/dashboard/databases/page.tsx
index 21be9fe..dd696b2 100644
--- a/src/app/dashboard/databases/page.tsx
+++ b/src/app/dashboard/databases/page.tsx
@@ -559,16 +559,16 @@ function BackupTab({ db }: { db: Db }) {
Google Drive Kurulumu
{[
- { n: 1, text: 'console.cloud.google.com → Yeni proje oluştur (veya mevcut)' },
+ { n: 1, text: 'console.cloud.google.com → Proje seç/oluştur' },
{ n: 2, text: '"APIs & Services" → "Enable APIs" → "Google Drive API" aç' },
- { n: 3, text: '"Credentials" → "Service Accounts" → Yeni hesap oluştur' },
- { n: 4, text: 'Hesaba tıkla → "Keys" → "Add Key" → "JSON" — dosyayı indir' },
- { n: 5, text: 'Google Drive\'da yeni klasör aç → Sağ tık → Paylaş' },
- { n: 6, text: 'Aşağıdaki e-postayı "Düzenleyici" olarak ekle ve klasör ID\'sini kopyala' },
+ { n: 3, text: '"Credentials" → "Service Accounts" → Yeni hesap oluştur → JSON key indir' },
+ { n: 4, text: 'Google Drive\'da bir klasör oluştur (örn. "VPS Backups")' },
+ { n: 5, text: 'Klasörü sağ tıkla → "Paylaş" → aşağıdaki service account e-postasını "Düzenleyici" olarak ekle', highlight: true },
+ { n: 6, text: 'Klasörü aç → URL\'deki /folders/XXXXX kısmını Klasör ID alanına yapıştır', highlight: true },
].map(s => (
-
-
{s.n}
-
{s.text}
+
+ {s.n}
+ {s.text}
))}
diff --git a/src/lib/backup.ts b/src/lib/backup.ts
index dd66689..a57b250 100644
--- a/src/lib/backup.ts
+++ b/src/lib/backup.ts
@@ -14,10 +14,11 @@ export interface Database {
db_type?: 'postgres' | 'mysql'
}
-export function backupFilename(): string {
+export function backupFilename(dbName?: string): string {
const now = new Date()
const pad = (n: number) => String(n).padStart(2, '0')
- return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}.sql`
+ const ts = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`
+ return dbName ? `${dbName}_${ts}.sql` : `${ts}.sql`
}
export async function createDbDumpBuffer(db: Database): Promise
{
@@ -122,49 +123,6 @@ async function getGoogleAccessToken(credentials: Record): Promis
return access_token
}
-async function getOrCreateDriveFolder(
- accessToken: string,
- folderName: string,
- parentId?: string
-): Promise {
- // Aynı isimli klasör var mı kontrol et
- const parentQuery = parentId ? ` and '${parentId}' in parents` : ''
- const q = `name='${folderName.replace(/'/g, "\\'")}' and mimeType='application/vnd.google-apps.folder' and trashed=false${parentQuery}`
-
- const searchRes = await fetch(
- `https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(q)}&fields=files(id,name)`,
- { headers: { Authorization: `Bearer ${accessToken}` } }
- )
-
- if (searchRes.ok) {
- const { files } = await searchRes.json()
- if (files?.length > 0) return files[0].id
- }
-
- // Yoksa oluştur
- const meta: Record = {
- name: folderName,
- mimeType: 'application/vnd.google-apps.folder',
- }
- if (parentId) meta.parents = [parentId]
-
- const createRes = await fetch('https://www.googleapis.com/drive/v3/files', {
- method: 'POST',
- headers: {
- Authorization: `Bearer ${accessToken}`,
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(meta),
- })
-
- if (!createRes.ok) {
- throw new Error(`Klasör oluşturulamadı: ${await createRes.text()}`)
- }
-
- const folder = await createRes.json()
- return folder.id
-}
-
export async function uploadToGoogleDrive(
serviceAccountJsonStr: string,
dbName: string,
@@ -172,15 +130,16 @@ export async function uploadToGoogleDrive(
content: Buffer,
parentFolderId?: string
) {
+ if (!parentFolderId) {
+ throw new Error('Klasör ID zorunlu. Drive\'da bir klasör oluşturun, service account ile paylaşın ve ID\'sini girin.')
+ }
+
const credentials = JSON.parse(serviceAccountJsonStr)
const accessToken = await getGoogleAccessToken(credentials)
- // DB adında alt klasör bul veya oluştur
- const targetFolderId = await getOrCreateDriveFolder(accessToken, dbName, parentFolderId || undefined)
-
- // Multipart upload
const boundary = 'vps_panel_backup_boundary'
- const meta = JSON.stringify({ name: filename, parents: [targetFolderId] })
+ // parents açıkça belirtilmezse service account kendi kotasız alanına yüklemeye çalışır
+ const meta = JSON.stringify({ name: filename, parents: [parentFolderId] })
const parts = Buffer.concat([
Buffer.from(`--${boundary}\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n${meta}\r\n`),
@@ -190,7 +149,7 @@ export async function uploadToGoogleDrive(
])
const response = await fetch(
- 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,name',
+ 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,name&supportsAllDrives=true',
{
method: 'POST',
headers: {
@@ -224,17 +183,17 @@ export async function testGoogleDriveCredentials(
if (folderId) {
const folderRes = await fetch(
- `https://www.googleapis.com/drive/v3/files/${folderId}?fields=id,name`,
+ `https://www.googleapis.com/drive/v3/files/${folderId}?fields=id,name&supportsAllDrives=true`,
{ headers: { Authorization: `Bearer ${accessToken}` } }
)
if (!folderRes.ok) {
return {
ok: false,
- error: `Token alındı ama klasöre erişilemiyor. "${credentials.client_email}" adresini klasörde Düzenleyici olarak eklediniz mi?`,
+ error: `Token alındı ama Drive'a erişilemiyor. Service account'u Paylaşımlı Drive'a üye olarak eklediniz mi? (${credentials.client_email})`,
}
}
const folder = await folderRes.json()
- return { ok: true, detail: `Bağlantı OK — Klasör: "${folder.name}" — Hesap: ${credentials.client_email}` }
+ return { ok: true, detail: `Bağlantı OK — Drive: "${folder.name}" — Hesap: ${credentials.client_email}` }
}
return { ok: true, detail: `Token alındı — Hesap: ${credentials.client_email}` }
diff --git a/src/lib/cronWorker.ts b/src/lib/cronWorker.ts
index b59b857..8b1c5d7 100644
--- a/src/lib/cronWorker.ts
+++ b/src/lib/cronWorker.ts
@@ -28,7 +28,7 @@ export async function reloadBackupCrons() {
console.log(`[Backup] ${db.name} (${row.cloud_type}) başlatılıyor...`)
const buffer = await createDbDumpBuffer(db)
- const filename = backupFilename()
+ const filename = backupFilename(db.name)
let message = 'Success'
try {