@riligar/elysia-backup 1.3.0 → 1.4.0
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/package.json +4 -4
- package/src/assets/favicon.ico +0 -0
- package/src/assets/logo.png +0 -0
- package/src/core/config.js +70 -0
- package/src/core/scheduler.js +99 -0
- package/src/core/session.js +90 -0
- package/src/index.js +609 -3
- package/src/middleware/auth.middleware.js +41 -0
- package/src/services/backup.service.js +169 -0
- package/src/services/s3-client.js +28 -0
- package/src/views/DashboardPage.js +56 -0
- package/src/views/LoginPage.js +23 -0
- package/src/views/components/ActionArea.js +48 -0
- package/src/views/components/FilesTab.js +111 -0
- package/src/views/components/Head.js +43 -0
- package/src/views/components/Header.js +54 -0
- package/src/views/components/LoginCard.js +114 -0
- package/src/views/components/SecuritySection.js +166 -0
- package/src/views/components/SettingsTab.js +88 -0
- package/src/views/components/StatusCards.js +54 -0
- package/src/views/scripts/backupApp.js +324 -0
- package/src/views/scripts/loginApp.js +60 -0
- package/src/elysia-backup.js +0 -1736
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riligar/elysia-backup",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Elysia plugin for R2/S3 backup with a built-in UI dashboard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
},
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
22
|
-
"url": "git+https://github.com/riligar/elysia-backup.git"
|
|
22
|
+
"url": "git+https://github.com/riligar-solutions/elysia-backup.git"
|
|
23
23
|
},
|
|
24
|
-
"homepage": "https://github.com/riligar/elysia-backup#readme",
|
|
24
|
+
"homepage": "https://github.com/riligar-solutions/elysia-backup#readme",
|
|
25
25
|
"bugs": {
|
|
26
|
-
"url": "https://github.com/riligar/elysia-backup/issues"
|
|
26
|
+
"url": "https://github.com/riligar-solutions/elysia-backup/issues"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"dev": "bun run demo/index.js"
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'node:fs'
|
|
2
|
+
import { writeFile } from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Load configuration from a JSON file
|
|
6
|
+
* @param {string} configPath - Path to the config file
|
|
7
|
+
* @returns {Object} Loaded configuration or empty object
|
|
8
|
+
*/
|
|
9
|
+
export const loadConfig = configPath => {
|
|
10
|
+
if (!existsSync(configPath)) {
|
|
11
|
+
return {}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const fileContent = readFileSync(configPath, 'utf-8')
|
|
16
|
+
const config = JSON.parse(fileContent)
|
|
17
|
+
console.log('Loaded backup config from', configPath)
|
|
18
|
+
return config
|
|
19
|
+
} catch (e) {
|
|
20
|
+
console.error('Failed to load backup config:', e)
|
|
21
|
+
return {}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Save configuration to a JSON file
|
|
27
|
+
* @param {string} configPath - Path to save the config
|
|
28
|
+
* @param {Object} config - Configuration object to save
|
|
29
|
+
*/
|
|
30
|
+
export const saveConfig = async (configPath, config) => {
|
|
31
|
+
try {
|
|
32
|
+
await writeFile(configPath, JSON.stringify(config, null, 2))
|
|
33
|
+
return true
|
|
34
|
+
} catch (e) {
|
|
35
|
+
console.error('Failed to save config:', e)
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a configuration manager
|
|
42
|
+
* @param {Object} initialConfig - Initial configuration from code
|
|
43
|
+
* @returns {Object} Configuration manager with get/set/save methods
|
|
44
|
+
*/
|
|
45
|
+
export const createConfigManager = initialConfig => {
|
|
46
|
+
const configPath = initialConfig.configPath || './config.json'
|
|
47
|
+
const savedConfig = loadConfig(configPath)
|
|
48
|
+
|
|
49
|
+
let config = { ...initialConfig, ...savedConfig }
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
get: () => config,
|
|
53
|
+
getPath: () => configPath,
|
|
54
|
+
|
|
55
|
+
set: newConfig => {
|
|
56
|
+
config = { ...config, ...newConfig }
|
|
57
|
+
return config
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
save: async () => {
|
|
61
|
+
return saveConfig(configPath, config)
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
update: async newConfig => {
|
|
65
|
+
config = { ...config, ...newConfig }
|
|
66
|
+
await saveConfig(configPath, config)
|
|
67
|
+
return config
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { CronJob } from 'cron'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create a backup scheduler
|
|
5
|
+
* @param {Function} onBackup - Callback to execute on scheduled backup
|
|
6
|
+
* @returns {Object} Scheduler with setup/stop/getStatus methods
|
|
7
|
+
*/
|
|
8
|
+
export const createScheduler = onBackup => {
|
|
9
|
+
let backupJob = null
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
/**
|
|
13
|
+
* Setup or restart the cron scheduler
|
|
14
|
+
* @param {string} cronSchedule - Cron expression
|
|
15
|
+
* @param {boolean} enabled - Whether scheduling is enabled
|
|
16
|
+
*/
|
|
17
|
+
setup: (cronSchedule, enabled = true) => {
|
|
18
|
+
// Stop existing job if any
|
|
19
|
+
if (backupJob) {
|
|
20
|
+
backupJob.stop()
|
|
21
|
+
backupJob = null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!cronSchedule || enabled === false) {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(`Setting up backup cron: ${cronSchedule}`)
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
backupJob = new CronJob(
|
|
32
|
+
cronSchedule,
|
|
33
|
+
async () => {
|
|
34
|
+
console.log('Running scheduled backup...')
|
|
35
|
+
try {
|
|
36
|
+
// Generate timestamp for the backup
|
|
37
|
+
const now = new Date()
|
|
38
|
+
const timestamp =
|
|
39
|
+
now.getFullYear() +
|
|
40
|
+
'-' +
|
|
41
|
+
String(now.getMonth() + 1).padStart(2, '0') +
|
|
42
|
+
'-' +
|
|
43
|
+
String(now.getDate()).padStart(2, '0') +
|
|
44
|
+
'_' +
|
|
45
|
+
String(now.getHours()).padStart(2, '0') +
|
|
46
|
+
'-' +
|
|
47
|
+
String(now.getMinutes()).padStart(2, '0') +
|
|
48
|
+
'-' +
|
|
49
|
+
String(now.getSeconds()).padStart(2, '0')
|
|
50
|
+
|
|
51
|
+
await onBackup(timestamp)
|
|
52
|
+
console.log('Scheduled backup completed')
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error('Scheduled backup failed:', e)
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
null,
|
|
58
|
+
true // start immediately
|
|
59
|
+
)
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.error('Invalid cron schedule:', e.message)
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Stop the scheduler
|
|
67
|
+
*/
|
|
68
|
+
stop: () => {
|
|
69
|
+
if (backupJob) {
|
|
70
|
+
backupJob.stop()
|
|
71
|
+
backupJob = null
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get current scheduler status
|
|
77
|
+
* @param {boolean} cronEnabled - Whether cron is enabled in config
|
|
78
|
+
* @returns {Object} Status with isRunning and nextRun
|
|
79
|
+
*/
|
|
80
|
+
getStatus: cronEnabled => {
|
|
81
|
+
const isRunning = !!backupJob && cronEnabled !== false
|
|
82
|
+
let nextRun = null
|
|
83
|
+
|
|
84
|
+
if (isRunning && backupJob) {
|
|
85
|
+
try {
|
|
86
|
+
const nextDate = backupJob.nextDate()
|
|
87
|
+
if (nextDate) {
|
|
88
|
+
// cron returns Luxon DateTime, convert to JS Date
|
|
89
|
+
nextRun = nextDate.toJSDate().toISOString()
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
console.error('Error getting next date', e)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { isRunning, nextRun }
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Management Module
|
|
3
|
+
* Handles secure session creation, validation, and cleanup
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate a secure random session token
|
|
8
|
+
* @returns {string} 64-character hex token
|
|
9
|
+
*/
|
|
10
|
+
export const generateSessionToken = () => {
|
|
11
|
+
const array = new Uint8Array(32)
|
|
12
|
+
crypto.getRandomValues(array)
|
|
13
|
+
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a session manager instance
|
|
18
|
+
* @returns {Object} Session manager with create/get/delete methods
|
|
19
|
+
*/
|
|
20
|
+
export const createSessionManager = () => {
|
|
21
|
+
const sessions = new Map()
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
/**
|
|
25
|
+
* Create a new session for a user
|
|
26
|
+
* @param {string} username - Username for the session
|
|
27
|
+
* @param {number} sessionDuration - Duration in milliseconds (default: 24h)
|
|
28
|
+
* @returns {Object} Session token and expiration
|
|
29
|
+
*/
|
|
30
|
+
create: (username, sessionDuration = 24 * 60 * 60 * 1000) => {
|
|
31
|
+
const token = generateSessionToken()
|
|
32
|
+
const expiresAt = Date.now() + sessionDuration
|
|
33
|
+
sessions.set(token, { username, expiresAt })
|
|
34
|
+
return { token, expiresAt }
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get and validate a session by token
|
|
39
|
+
* @param {string} token - Session token
|
|
40
|
+
* @returns {Object|null} Session data or null if invalid/expired
|
|
41
|
+
*/
|
|
42
|
+
get: token => {
|
|
43
|
+
if (!token) return null
|
|
44
|
+
|
|
45
|
+
const session = sessions.get(token)
|
|
46
|
+
if (!session) return null
|
|
47
|
+
|
|
48
|
+
if (Date.now() > session.expiresAt) {
|
|
49
|
+
sessions.delete(token)
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return session
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Delete a session
|
|
58
|
+
* @param {string} token - Session token to delete
|
|
59
|
+
*/
|
|
60
|
+
delete: token => {
|
|
61
|
+
if (token) {
|
|
62
|
+
sessions.delete(token)
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get the number of active sessions
|
|
68
|
+
* @returns {number} Count of active sessions
|
|
69
|
+
*/
|
|
70
|
+
count: () => sessions.size,
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Clean up expired sessions
|
|
74
|
+
* @returns {number} Number of sessions cleaned
|
|
75
|
+
*/
|
|
76
|
+
cleanup: () => {
|
|
77
|
+
const now = Date.now()
|
|
78
|
+
let cleaned = 0
|
|
79
|
+
|
|
80
|
+
for (const [token, session] of sessions) {
|
|
81
|
+
if (now > session.expiresAt) {
|
|
82
|
+
sessions.delete(token)
|
|
83
|
+
cleaned++
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return cleaned
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
}
|