@cybermem/dashboard 0.5.8 → 0.5.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/api/backup/route.ts +12 -14
- package/app/api/reset/route.ts +24 -38
- package/app/api/restore/route.ts +17 -37
- package/package.json +1 -1
package/app/api/backup/route.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createReadStream, statSync } from 'fs'
|
|
1
|
+
import { execSync } from 'child_process'
|
|
2
|
+
import { createReadStream, rmSync, statSync } from 'fs'
|
|
3
3
|
import { NextResponse } from 'next/server'
|
|
4
4
|
import { tmpdir } from 'os'
|
|
5
5
|
import { join } from 'path'
|
|
6
|
-
import { promisify } from 'util'
|
|
7
6
|
|
|
8
7
|
export const dynamic = 'force-dynamic'
|
|
9
8
|
|
|
10
|
-
const
|
|
9
|
+
const DATA_DIR = process.env.DATA_DIR || '/data'
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* GET /api/backup
|
|
@@ -19,14 +18,9 @@ export async function GET() {
|
|
|
19
18
|
const backupName = `cybermem-backup-${timestamp}.tar.gz`
|
|
20
19
|
const tmpPath = join(tmpdir(), backupName)
|
|
21
20
|
|
|
22
|
-
// Create backup via docker
|
|
23
|
-
const containerName = process.env.OPENMEMORY_CONTAINER || 'cybermem-openmemory'
|
|
24
|
-
|
|
25
21
|
try {
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
`docker cp ${containerName}:/data - | gzip > "${tmpPath}"`
|
|
29
|
-
)
|
|
22
|
+
// Create backup via tar command (available in Node alpine image)
|
|
23
|
+
execSync(`tar -czf "${tmpPath}" -C "${DATA_DIR}" .`, { stdio: 'pipe' })
|
|
30
24
|
|
|
31
25
|
// Get file stats
|
|
32
26
|
const stats = statSync(tmpPath)
|
|
@@ -38,7 +32,11 @@ export async function GET() {
|
|
|
38
32
|
const webStream = new ReadableStream({
|
|
39
33
|
start(controller) {
|
|
40
34
|
stream.on('data', (chunk) => controller.enqueue(chunk))
|
|
41
|
-
stream.on('end', () =>
|
|
35
|
+
stream.on('end', () => {
|
|
36
|
+
controller.close()
|
|
37
|
+
// Clean up temp file after streaming
|
|
38
|
+
try { rmSync(tmpPath) } catch {}
|
|
39
|
+
})
|
|
42
40
|
stream.on('error', (err) => controller.error(err))
|
|
43
41
|
}
|
|
44
42
|
})
|
|
@@ -51,9 +49,9 @@ export async function GET() {
|
|
|
51
49
|
}
|
|
52
50
|
})
|
|
53
51
|
|
|
54
|
-
} catch (
|
|
52
|
+
} catch (tarError: any) {
|
|
55
53
|
return NextResponse.json(
|
|
56
|
-
{ error: `Backup failed: ${
|
|
54
|
+
{ error: `Backup failed: ${tarError.message}` },
|
|
57
55
|
{ status: 500 }
|
|
58
56
|
)
|
|
59
57
|
}
|
package/app/api/reset/route.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readdirSync, statSync, unlinkSync } from 'fs'
|
|
2
2
|
import { NextRequest, NextResponse } from 'next/server'
|
|
3
|
-
import {
|
|
3
|
+
import { join } from 'path'
|
|
4
4
|
|
|
5
5
|
export const dynamic = 'force-dynamic'
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const DATA_DIR = process.env.DATA_DIR || '/data'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* POST /api/reset
|
|
@@ -24,52 +24,38 @@ export async function POST(request: NextRequest) {
|
|
|
24
24
|
)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
//
|
|
28
|
-
const containerName = process.env.OPENMEMORY_CONTAINER || 'cybermem-openmemory'
|
|
29
|
-
|
|
27
|
+
// Remove SQLite files directly via volume mount
|
|
30
28
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`docker exec ${containerName} sh -c 'rm -f /data/openmemory.sqlite*'`
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
// Restart container to reinitialize
|
|
37
|
-
await execAsync(`docker restart ${containerName}`)
|
|
29
|
+
const files = readdirSync(DATA_DIR)
|
|
30
|
+
let deletedCount = 0
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
32
|
+
for (const file of files) {
|
|
33
|
+
if (file.startsWith('openmemory.sqlite')) {
|
|
34
|
+
const filePath = join(DATA_DIR, file)
|
|
35
|
+
try {
|
|
36
|
+
const stat = statSync(filePath)
|
|
37
|
+
if (stat.isFile()) {
|
|
38
|
+
unlinkSync(filePath)
|
|
39
|
+
deletedCount++
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// File may already be deleted
|
|
49
43
|
}
|
|
50
|
-
} catch {
|
|
51
|
-
// Still starting up
|
|
52
44
|
}
|
|
53
45
|
}
|
|
54
46
|
|
|
55
|
-
|
|
56
|
-
return NextResponse.json(
|
|
57
|
-
{ error: 'Database reset but container failed to become healthy' },
|
|
58
|
-
{ status: 500 }
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Restart log-exporter and db-exporter
|
|
63
|
-
await execAsync('docker restart cybermem-log-exporter cybermem-db-exporter').catch(() => {})
|
|
64
|
-
|
|
47
|
+
// Notify user that container restart is needed
|
|
65
48
|
return NextResponse.json({
|
|
66
49
|
success: true,
|
|
67
|
-
message:
|
|
50
|
+
message: `Deleted ${deletedCount} database files. Restart openmemory container to reinitialize.`,
|
|
51
|
+
deletedCount,
|
|
52
|
+
restartRequired: true,
|
|
53
|
+
restartCommand: 'docker restart cybermem-openmemory'
|
|
68
54
|
})
|
|
69
55
|
|
|
70
|
-
} catch (
|
|
56
|
+
} catch (fsError: any) {
|
|
71
57
|
return NextResponse.json(
|
|
72
|
-
{ error: `
|
|
58
|
+
{ error: `File operation failed: ${fsError.message}` },
|
|
73
59
|
{ status: 500 }
|
|
74
60
|
)
|
|
75
61
|
}
|
package/app/api/restore/route.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { unlinkSync, writeFileSync } from 'fs'
|
|
1
|
+
import { execSync } from 'child_process'
|
|
2
|
+
import { readdirSync, rmSync, unlinkSync, writeFileSync } from 'fs'
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server'
|
|
4
4
|
import { tmpdir } from 'os'
|
|
5
5
|
import { join } from 'path'
|
|
6
|
-
import { promisify } from 'util'
|
|
7
6
|
|
|
8
7
|
export const dynamic = 'force-dynamic'
|
|
9
8
|
|
|
10
|
-
const
|
|
9
|
+
const DATA_DIR = process.env.DATA_DIR || '/data'
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* POST /api/restore
|
|
@@ -35,7 +34,6 @@ export async function POST(request: NextRequest) {
|
|
|
35
34
|
)
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
const containerName = process.env.OPENMEMORY_CONTAINER || 'cybermem-openmemory'
|
|
39
37
|
const tmpPath = join(tmpdir(), `restore-${Date.now()}.tar.gz`)
|
|
40
38
|
|
|
41
39
|
try {
|
|
@@ -43,51 +41,33 @@ export async function POST(request: NextRequest) {
|
|
|
43
41
|
const buffer = Buffer.from(await file.arrayBuffer())
|
|
44
42
|
writeFileSync(tmpPath, buffer)
|
|
45
43
|
|
|
46
|
-
//
|
|
47
|
-
|
|
44
|
+
// Remove existing database files
|
|
45
|
+
const existingFiles = readdirSync(DATA_DIR)
|
|
46
|
+
for (const f of existingFiles) {
|
|
47
|
+
if (f.startsWith('openmemory.sqlite')) {
|
|
48
|
+
try { rmSync(join(DATA_DIR, f)) } catch {}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
48
51
|
|
|
49
|
-
// Extract backup to
|
|
50
|
-
|
|
51
|
-
`gunzip -c "${tmpPath}" | docker cp - ${containerName}:/`
|
|
52
|
-
)
|
|
52
|
+
// Extract backup to data directory
|
|
53
|
+
execSync(`tar -xzf "${tmpPath}" -C "${DATA_DIR}"`, { stdio: 'pipe' })
|
|
53
54
|
|
|
54
55
|
// Clean up temp file
|
|
55
56
|
unlinkSync(tmpPath)
|
|
56
57
|
|
|
57
|
-
// Start container
|
|
58
|
-
await execAsync(`docker start ${containerName}`)
|
|
59
|
-
|
|
60
|
-
// Wait for health
|
|
61
|
-
let healthy = false
|
|
62
|
-
for (let i = 0; i < 30; i++) {
|
|
63
|
-
await new Promise(r => setTimeout(r, 2000))
|
|
64
|
-
try {
|
|
65
|
-
const healthUrl = process.env.CYBERMEM_URL || 'http://localhost:8626'
|
|
66
|
-
const res = await fetch(`${healthUrl}/health`, { cache: 'no-store' })
|
|
67
|
-
if (res.ok) {
|
|
68
|
-
healthy = true
|
|
69
|
-
break
|
|
70
|
-
}
|
|
71
|
-
} catch {
|
|
72
|
-
// Still starting up
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Restart exporters
|
|
77
|
-
await execAsync('docker restart cybermem-log-exporter cybermem-db-exporter').catch(() => {})
|
|
78
|
-
|
|
79
58
|
return NextResponse.json({
|
|
80
59
|
success: true,
|
|
81
|
-
message: 'Database restored successfully',
|
|
82
|
-
|
|
60
|
+
message: 'Database restored successfully. Restart openmemory container to apply.',
|
|
61
|
+
restartRequired: true,
|
|
62
|
+
restartCommand: 'docker restart cybermem-openmemory'
|
|
83
63
|
})
|
|
84
64
|
|
|
85
|
-
} catch (
|
|
65
|
+
} catch (restoreError: any) {
|
|
86
66
|
// Clean up temp file on error
|
|
87
67
|
try { unlinkSync(tmpPath) } catch {}
|
|
88
68
|
|
|
89
69
|
return NextResponse.json(
|
|
90
|
-
{ error: `Restore failed: ${
|
|
70
|
+
{ error: `Restore failed: ${restoreError.message}` },
|
|
91
71
|
{ status: 500 }
|
|
92
72
|
)
|
|
93
73
|
}
|