@live-change/backup-service 0.9.208 → 0.9.212
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/backup.js +118 -46
- package/clear.js +31 -0
- package/index.js +134 -32
- package/package.json +5 -5
package/backup.js
CHANGED
|
@@ -9,6 +9,7 @@ import { exec as cpExec } from 'child_process'
|
|
|
9
9
|
const exec = util.promisify(cpExec)
|
|
10
10
|
import dump from '@live-change/db-client/lib/dump.js'
|
|
11
11
|
import { once } from 'events'
|
|
12
|
+
import { finished } from 'node:stream/promises'
|
|
12
13
|
import os from "os"
|
|
13
14
|
import path from 'path'
|
|
14
15
|
|
|
@@ -17,6 +18,14 @@ function currentBackupPath(backupsDir = './backups/') {
|
|
|
17
18
|
return `${backupsDir}/${dateString.substring(0, dateString.length - 1)}`
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
function createBackupLogger(backupsDir) {
|
|
22
|
+
const logPath = path.join(path.resolve(backupsDir), 'backups.log')
|
|
23
|
+
return async function log(message) {
|
|
24
|
+
const line = `${new Date().toISOString()} ${message}\n`
|
|
25
|
+
await fs.promises.appendFile(logPath, line, { encoding: 'utf8' })
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
async function writeDbBackup(stream) {
|
|
21
30
|
let drainPromise
|
|
22
31
|
async function write(object) {
|
|
@@ -27,11 +36,10 @@ async function writeDbBackup(stream) {
|
|
|
27
36
|
drainPromise = null
|
|
28
37
|
}
|
|
29
38
|
}
|
|
30
|
-
|
|
39
|
+
try {
|
|
40
|
+
await dump({
|
|
31
41
|
dao: app.dao,
|
|
32
42
|
db: app.databaseName,
|
|
33
|
-
//serverUrl: process.env.DB_URL || 'http://localhost:9417/api/ws',
|
|
34
|
-
//db: process.env.DB_NAME || 'test',
|
|
35
43
|
structure: true,
|
|
36
44
|
verbose: true,
|
|
37
45
|
bucket: 128, // less database stress
|
|
@@ -39,64 +47,128 @@ async function writeDbBackup(stream) {
|
|
|
39
47
|
},
|
|
40
48
|
(method, ...args) => write({ type: 'request', method, parameters: args }),
|
|
41
49
|
() => write({ type: 'sync' })
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
)
|
|
51
|
+
stream.end()
|
|
52
|
+
await finished(stream)
|
|
53
|
+
} catch (err) {
|
|
54
|
+
stream.destroy()
|
|
55
|
+
throw err
|
|
56
|
+
}
|
|
44
57
|
}
|
|
45
58
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
)
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
function parseBackupTimestamp(basename) {
|
|
60
|
+
const match = basename.match(/^(\d{4})_(\d{2})_(\d{2})_(\d{2})_(\d{2})_(\d{2})_(\d+)/)
|
|
61
|
+
if(!match) return null
|
|
62
|
+
const [_full, year, month, day, hour, minute, second, millis] = match
|
|
63
|
+
return new Date(`${year}-${month}-${day}T${hour}:${minute}:${second}.${millis}Z`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function createBackup(backupPath, log) {
|
|
67
|
+
const resolvedPath = path.resolve(backupPath)
|
|
68
|
+
const parentDir = path.dirname(resolvedPath)
|
|
69
|
+
const base = path.basename(resolvedPath)
|
|
70
|
+
const tarTmpPath = path.join(parentDir, `${base}.tar.gz.tmp`)
|
|
71
|
+
|
|
72
|
+
await log(`createBackup start backupPath=${resolvedPath}`)
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
await fs.promises.mkdir(resolvedPath)
|
|
76
|
+
await log('mkdir done')
|
|
77
|
+
|
|
78
|
+
const dbStream = fs.createWriteStream(path.resolve(resolvedPath, 'db.json'))
|
|
79
|
+
await writeDbBackup(dbStream)
|
|
80
|
+
await log('db dump finished (stream closed)')
|
|
81
|
+
|
|
82
|
+
const version = await (
|
|
83
|
+
fs.promises.readFile('./package.json', { encoding: 'utf8' })
|
|
84
|
+
.catch(e => 'unknown')
|
|
85
|
+
.then(data => JSON.parse(data).version)
|
|
86
|
+
)
|
|
87
|
+
const info = {
|
|
88
|
+
version: version,
|
|
89
|
+
hostname: os.hostname(),
|
|
90
|
+
directory: path.resolve('.')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
await fs.promises.writeFile(path.resolve(resolvedPath, 'info.json'), JSON.stringify(info, null, ' '))
|
|
94
|
+
await log('info.json written')
|
|
62
95
|
|
|
63
|
-
|
|
96
|
+
await fse.copy("./storage", path.resolve(resolvedPath, "storage"))
|
|
97
|
+
await log('storage copied')
|
|
64
98
|
|
|
65
|
-
|
|
99
|
+
const command = `tar -zcf ../${base}.tar.gz.tmp storage info.json db.json`
|
|
100
|
+
console.log("EXEC TAR COMMAND:", command)
|
|
101
|
+
await log(`tar start command=${command}`)
|
|
102
|
+
await exec(command, { cwd: resolvedPath })
|
|
103
|
+
await log('tar done')
|
|
66
104
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
await exec(command, { cwd: backupPath })
|
|
105
|
+
await fs.promises.rename(tarTmpPath, path.join(parentDir, `${base}.tar.gz`))
|
|
106
|
+
await log('archive renamed to .tar.gz')
|
|
70
107
|
|
|
71
|
-
|
|
108
|
+
await fse.remove(resolvedPath)
|
|
109
|
+
await log('temp directory removed')
|
|
72
110
|
|
|
73
|
-
|
|
111
|
+
await log(`createBackup success backupPath=${resolvedPath}`)
|
|
112
|
+
} catch (err) {
|
|
113
|
+
const detail = err && err.stack ? err.stack : String(err)
|
|
114
|
+
await log(`createBackup error: ${detail}`)
|
|
115
|
+
try {
|
|
116
|
+
if(await fse.pathExists(resolvedPath)) {
|
|
117
|
+
await fse.remove(resolvedPath)
|
|
118
|
+
await log(`cleanup removed temp directory ${resolvedPath}`)
|
|
119
|
+
}
|
|
120
|
+
} catch (cleanupErr) {
|
|
121
|
+
await log(`cleanup temp directory failed: ${cleanupErr}`)
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
if(await fse.pathExists(tarTmpPath)) {
|
|
125
|
+
await fse.remove(tarTmpPath)
|
|
126
|
+
await log(`cleanup removed ${tarTmpPath}`)
|
|
127
|
+
}
|
|
128
|
+
} catch (cleanupErr) {
|
|
129
|
+
await log(`cleanup tar tmp failed: ${cleanupErr}`)
|
|
130
|
+
}
|
|
131
|
+
throw err
|
|
132
|
+
}
|
|
74
133
|
}
|
|
75
134
|
|
|
76
|
-
async function removeOldBackups(backupsDir = '../../backups/', maxAge = 10*24*3600*1000, minBackups = 10) {
|
|
77
|
-
const
|
|
135
|
+
async function removeOldBackups(backupsDir = '../../backups/', maxAge = 10*24*3600*1000, minBackups = 10, log) {
|
|
136
|
+
const resolvedDir = path.resolve(backupsDir)
|
|
137
|
+
const dirents = await fs.promises.readdir(resolvedDir, { withFileTypes: true })
|
|
78
138
|
const now = Date.now()
|
|
79
|
-
|
|
80
|
-
const backups =
|
|
81
|
-
|
|
82
|
-
if(!
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// sort by date, from oldest to newest
|
|
139
|
+
|
|
140
|
+
const backups = []
|
|
141
|
+
for(const dirent of dirents) {
|
|
142
|
+
if(!dirent.isFile()) continue
|
|
143
|
+
const file = dirent.name
|
|
144
|
+
if(!file.endsWith('.tar.gz')) continue
|
|
145
|
+
const date = parseBackupTimestamp(file.replace(/\.tar\.gz$/, ''))
|
|
146
|
+
if(!date || Number.isNaN(date.getTime())) continue
|
|
147
|
+
backups.push({ file, date })
|
|
148
|
+
}
|
|
149
|
+
|
|
91
150
|
backups.sort((a, b) => a.date - b.date)
|
|
92
151
|
const olderBackups = backups
|
|
93
|
-
.slice(0, backups.length - minBackups)
|
|
94
|
-
.filter(backup => now - backup.date > maxAge)
|
|
152
|
+
.slice(0, backups.length - minBackups)
|
|
153
|
+
.filter(backup => now - backup.date > maxAge)
|
|
95
154
|
for(const backup of olderBackups) {
|
|
96
|
-
const backupPath = path.
|
|
155
|
+
const backupPath = path.join(resolvedDir, backup.file)
|
|
97
156
|
console.log("REMOVING OLD BACKUP:", backupPath)
|
|
157
|
+
if(log) await log(`removeOldBackups removing archive ${backupPath}`)
|
|
98
158
|
await fse.remove(backupPath)
|
|
99
159
|
}
|
|
160
|
+
|
|
161
|
+
for(const dirent of dirents) {
|
|
162
|
+
if(!dirent.isDirectory()) continue
|
|
163
|
+
const name = dirent.name
|
|
164
|
+
const date = parseBackupTimestamp(name)
|
|
165
|
+
if(!date || Number.isNaN(date.getTime())) continue
|
|
166
|
+
if(now - date <= maxAge) continue
|
|
167
|
+
const dirPath = path.join(resolvedDir, name)
|
|
168
|
+
console.log("REMOVING OLD BACKUP DIRECTORY:", dirPath)
|
|
169
|
+
if(log) await log(`removeOldBackups removing stale directory ${dirPath}`)
|
|
170
|
+
await fse.remove(dirPath)
|
|
171
|
+
}
|
|
100
172
|
}
|
|
101
173
|
|
|
102
|
-
export { createBackup, currentBackupPath, removeOldBackups }
|
|
174
|
+
export { createBackup, createBackupLogger, currentBackupPath, removeOldBackups }
|
package/clear.js
CHANGED
|
@@ -97,4 +97,35 @@ export async function removeOldCommands(before, options) {
|
|
|
97
97
|
if(options.delay) await new Promise(resolve => setTimeout(resolve, options.delay))
|
|
98
98
|
more = deleteStats.count >= bucket
|
|
99
99
|
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const clearEventReportsQuery = `${async (input, output, { indexName, idPrefix, bucket, before }) => {
|
|
103
|
+
const indexReader = await input.index(indexName)
|
|
104
|
+
const tableWriter = await output.table('eventReports')
|
|
105
|
+
const pointers = await indexReader.rangeGet({ limit: bucket, lt: before })
|
|
106
|
+
for(const pointer of pointers) {
|
|
107
|
+
await tableWriter.delete(idPrefix + pointer.to)
|
|
108
|
+
}
|
|
109
|
+
await output.put({ count: pointers.length })
|
|
110
|
+
}}`
|
|
111
|
+
|
|
112
|
+
async function removeOldEventReportsForIndex(indexName, idPrefix, before, options) {
|
|
113
|
+
const bucket = options?.bucket || 128
|
|
114
|
+
let more = true
|
|
115
|
+
while(more) {
|
|
116
|
+
const queryResult = await app.dao.request(['database', 'query'], app.databaseName, clearEventReportsQuery,
|
|
117
|
+
{ indexName, idPrefix, bucket, before })
|
|
118
|
+
const deleteStats = queryResult[0]
|
|
119
|
+
if(options?.delay) await new Promise(resolve => setTimeout(resolve, options.delay))
|
|
120
|
+
more = deleteStats.count >= bucket
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function removeOldEventReports(commandBefore, triggerBefore, options = {}) {
|
|
125
|
+
if(!app.splitCommands && commandBefore) {
|
|
126
|
+
await removeOldEventReportsForIndex('commands_byTimestamp', 'command_', commandBefore, options)
|
|
127
|
+
}
|
|
128
|
+
if(!app.splitTriggers && triggerBefore) {
|
|
129
|
+
await removeOldEventReportsForIndex('triggers_byTimestamp', 'trigger_', triggerBefore, options)
|
|
130
|
+
}
|
|
100
131
|
}
|
package/index.js
CHANGED
|
@@ -8,17 +8,19 @@ import express from 'express'
|
|
|
8
8
|
import basicAuth from 'express-basic-auth'
|
|
9
9
|
const expressApp = express()
|
|
10
10
|
import fs from 'fs'
|
|
11
|
-
import { createBackup, currentBackupPath, removeOldBackups } from './backup.js'
|
|
11
|
+
import { createBackup, createBackupLogger, currentBackupPath, removeOldBackups } from './backup.js'
|
|
12
12
|
import { restoreBackup } from './restore.js'
|
|
13
13
|
import {
|
|
14
14
|
getLastEventId, removeOldEvents, removeOldOpLogs,
|
|
15
|
-
getLastCommandTimestamp, getLastTriggerTimestamp, removeOldTriggers, removeOldCommands
|
|
15
|
+
getLastCommandTimestamp, getLastTriggerTimestamp, removeOldTriggers, removeOldCommands,
|
|
16
|
+
removeOldEventReports
|
|
16
17
|
} from './clear.js'
|
|
17
18
|
|
|
18
19
|
import progress from "progress-stream"
|
|
19
20
|
|
|
20
21
|
const TWENTY_FOUR_HOURS = 24 * 3600 * 1000
|
|
21
22
|
const TEN_MINUTES = 10 * 60 * 1000
|
|
23
|
+
const DEFAULT_OP_LOG_RETENTION_MS = 60 * 60 * 1000
|
|
22
24
|
|
|
23
25
|
let currentBackup = null
|
|
24
26
|
|
|
@@ -30,6 +32,8 @@ const backupsDir = config.dir || './backups'
|
|
|
30
32
|
|
|
31
33
|
fs.mkdirSync(backupsDir, { recursive: true })
|
|
32
34
|
|
|
35
|
+
const backupLog = createBackupLogger(backupsDir)
|
|
36
|
+
|
|
33
37
|
let queue
|
|
34
38
|
(async () => {
|
|
35
39
|
const PQueue = (await import('p-queue')).default
|
|
@@ -41,7 +45,7 @@ if(backupServerUsername && backupServerPassword ) {
|
|
|
41
45
|
users[backupServerUsername] = backupServerPassword
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
if(Object.keys(users) > 0) {
|
|
48
|
+
if(Object.keys(users).length > 0) {
|
|
45
49
|
expressApp.use(basicAuth({
|
|
46
50
|
users,
|
|
47
51
|
challenge: true,
|
|
@@ -49,6 +53,87 @@ if(Object.keys(users) > 0) {
|
|
|
49
53
|
}))
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
function resolveRetentionMs(option) {
|
|
57
|
+
const value = config[option]
|
|
58
|
+
if(Number.isInteger(value)) return value
|
|
59
|
+
if(value === true && Number.isInteger(config.retentionMs)) return config.retentionMs
|
|
60
|
+
if(value !== false && Number.isInteger(config.retentionMs)) return config.retentionMs
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function shouldClear(option) {
|
|
65
|
+
const value = config[option]
|
|
66
|
+
if(value === false) return false
|
|
67
|
+
if(Number.isInteger(value)) return true
|
|
68
|
+
if(value === true) return true
|
|
69
|
+
if(Number.isInteger(config.retentionMs)) return true
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolveOpLogRetentionMs() {
|
|
74
|
+
if(Number.isInteger(config.opLogRetentionMs)) return config.opLogRetentionMs
|
|
75
|
+
if(config.clearOpLogs === true && Number.isInteger(config.retentionMs)) return config.retentionMs
|
|
76
|
+
if(Number.isInteger(config.clearOpLogs)) return config.clearOpLogs
|
|
77
|
+
return DEFAULT_OP_LOG_RETENTION_MS
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolveEventReportsRetentionMs() {
|
|
81
|
+
if(Number.isInteger(config.clearEventReports)) return config.clearEventReports
|
|
82
|
+
return resolveRetentionMs('clearEventReports')
|
|
83
|
+
?? resolveRetentionMs('clearCommands')
|
|
84
|
+
?? resolveRetentionMs('clearTriggers')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function computeTimestampCutoff(lastTimestamp, retentionMs) {
|
|
88
|
+
if(retentionMs === null) return null
|
|
89
|
+
const date = new Date(lastTimestamp)
|
|
90
|
+
if(!Number.isInteger(date.getTime())) return null
|
|
91
|
+
return new Date(date.getTime() - retentionMs).toISOString()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function computeCutoffs({ lastEventId, lastTriggerTimestamp, lastCommandTimestamp }) {
|
|
95
|
+
const eventsRetention = resolveRetentionMs('clearEvents')
|
|
96
|
+
let eventsBefore = lastEventId
|
|
97
|
+
if(shouldClear('clearEvents') && eventsRetention !== null && lastEventId) {
|
|
98
|
+
const ts = (+(lastEventId.split(':')[0]) - eventsRetention)
|
|
99
|
+
eventsBefore = ts.toFixed(0).padStart(16, '0') + ':'
|
|
100
|
+
} else if(!shouldClear('clearEvents')) {
|
|
101
|
+
eventsBefore = null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const triggersRetention = resolveRetentionMs('clearTriggers')
|
|
105
|
+
let triggersBefore = lastTriggerTimestamp
|
|
106
|
+
if(shouldClear('clearTriggers')) {
|
|
107
|
+
triggersBefore = computeTimestampCutoff(lastTriggerTimestamp, triggersRetention)
|
|
108
|
+
} else {
|
|
109
|
+
triggersBefore = null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const commandsRetention = resolveRetentionMs('clearCommands')
|
|
113
|
+
let commandsBefore = lastCommandTimestamp
|
|
114
|
+
if(shouldClear('clearCommands')) {
|
|
115
|
+
commandsBefore = computeTimestampCutoff(lastCommandTimestamp, commandsRetention)
|
|
116
|
+
} else {
|
|
117
|
+
commandsBefore = null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const eventReportsRetention = resolveEventReportsRetentionMs()
|
|
121
|
+
let eventReportsCommandsBefore = null
|
|
122
|
+
let eventReportsTriggersBefore = null
|
|
123
|
+
if(shouldClear('clearEventReports') && eventReportsRetention !== null) {
|
|
124
|
+
eventReportsCommandsBefore = computeTimestampCutoff(lastCommandTimestamp, eventReportsRetention)
|
|
125
|
+
eventReportsTriggersBefore = computeTimestampCutoff(lastTriggerTimestamp, eventReportsRetention)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
eventsBefore,
|
|
130
|
+
triggersBefore,
|
|
131
|
+
commandsBefore,
|
|
132
|
+
eventReportsCommandsBefore,
|
|
133
|
+
eventReportsTriggersBefore,
|
|
134
|
+
opLogRetentionMs: shouldClear('clearOpLogs') ? resolveOpLogRetentionMs() : null
|
|
135
|
+
}
|
|
136
|
+
}
|
|
52
137
|
|
|
53
138
|
function doBackup() {
|
|
54
139
|
console.log("DOING BACKUP!")
|
|
@@ -62,46 +147,63 @@ function doBackup() {
|
|
|
62
147
|
let lastTriggerTimestamp = await getLastTriggerTimestamp()
|
|
63
148
|
let lastCommandTimestamp = await getLastCommandTimestamp()
|
|
64
149
|
|
|
65
|
-
await removeOldBackups(backupsDir, 10 * TWENTY_FOUR_HOURS, 10)
|
|
66
|
-
await createBackup(path)
|
|
150
|
+
await removeOldBackups(backupsDir, 10 * TWENTY_FOUR_HOURS, 10, backupLog)
|
|
151
|
+
await createBackup(path, backupLog)
|
|
67
152
|
|
|
68
153
|
console.log("====== CLEAR STATS:")
|
|
69
154
|
console.log("Last event id:", lastEventId)
|
|
70
155
|
console.log("Last trigger timestamp:", lastTriggerTimestamp)
|
|
71
156
|
console.log("Last command timestamp:", lastCommandTimestamp)
|
|
72
157
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if(
|
|
79
|
-
|
|
80
|
-
const old = new Date(date.getTime() - config.clearTriggers)
|
|
81
|
-
console.log("Clear triggers before", old)
|
|
82
|
-
lastTriggerTimestamp = old.toISOString()
|
|
158
|
+
const cutoffs = computeCutoffs({ lastEventId, lastTriggerTimestamp, lastCommandTimestamp })
|
|
159
|
+
|
|
160
|
+
if(cutoffs.eventsBefore) console.log("Clear events before", new Date(+(cutoffs.eventsBefore.split(':')[0])))
|
|
161
|
+
if(cutoffs.triggersBefore) console.log("Clear triggers before", cutoffs.triggersBefore)
|
|
162
|
+
if(cutoffs.commandsBefore) console.log("Clear commands before", cutoffs.commandsBefore)
|
|
163
|
+
if(cutoffs.eventReportsCommandsBefore) {
|
|
164
|
+
console.log("Clear eventReports (commands) before", cutoffs.eventReportsCommandsBefore)
|
|
83
165
|
}
|
|
84
|
-
if(
|
|
85
|
-
|
|
86
|
-
const old = new Date(date.getTime() - config.clearCommands)
|
|
87
|
-
console.log("Clear commands before", old)
|
|
88
|
-
lastCommandTimestamp = old.toISOString()
|
|
166
|
+
if(cutoffs.eventReportsTriggersBefore) {
|
|
167
|
+
console.log("Clear eventReports (triggers) before", cutoffs.eventReportsTriggersBefore)
|
|
89
168
|
}
|
|
90
169
|
|
|
91
170
|
console.log("====== CLEARING:")
|
|
92
|
-
console.log("Last remaining event id:",
|
|
93
|
-
console.log("Last remaining trigger timestamp:",
|
|
94
|
-
console.log("Last remaining command timestamp:",
|
|
95
|
-
|
|
96
|
-
if(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
171
|
+
console.log("Last remaining event id:", cutoffs.eventsBefore)
|
|
172
|
+
console.log("Last remaining trigger timestamp:", cutoffs.triggersBefore)
|
|
173
|
+
console.log("Last remaining command timestamp:", cutoffs.commandsBefore)
|
|
174
|
+
|
|
175
|
+
if(shouldClear('clearEventReports')) {
|
|
176
|
+
await backupLog('clearEventReports start')
|
|
177
|
+
await removeOldEventReports(
|
|
178
|
+
cutoffs.eventReportsCommandsBefore,
|
|
179
|
+
cutoffs.eventReportsTriggersBefore,
|
|
180
|
+
{ delay: 10 }
|
|
181
|
+
)
|
|
182
|
+
await backupLog('clearEventReports done')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if(shouldClear('clearCommands') && cutoffs.commandsBefore) {
|
|
186
|
+
await removeOldCommands(cutoffs.commandsBefore, { delay: 10 })
|
|
187
|
+
}
|
|
188
|
+
if(shouldClear('clearTriggers') && cutoffs.triggersBefore) {
|
|
189
|
+
await removeOldTriggers(cutoffs.triggersBefore, { delay: 10 })
|
|
190
|
+
}
|
|
191
|
+
if(shouldClear('clearEvents') && cutoffs.eventsBefore) {
|
|
192
|
+
await removeOldEvents(cutoffs.eventsBefore)
|
|
193
|
+
}
|
|
194
|
+
if(shouldClear('clearOpLogs') && cutoffs.opLogRetentionMs !== null) {
|
|
195
|
+
await removeOldOpLogs(cutoffs.opLogRetentionMs)
|
|
196
|
+
}
|
|
100
197
|
})
|
|
101
198
|
}
|
|
102
|
-
currentBackup.promise.then(
|
|
199
|
+
currentBackup.promise.then(async () => {
|
|
103
200
|
console.log("BACKUP CREATED!")
|
|
104
|
-
fs.promises.writeFile(backupsDir + '/latest.txt', filename+'.tar.gz')
|
|
201
|
+
await fs.promises.writeFile(backupsDir + '/latest.txt', filename+'.tar.gz')
|
|
202
|
+
currentBackup = null
|
|
203
|
+
}).catch(async err => {
|
|
204
|
+
const detail = err && err.stack ? err.stack : String(err)
|
|
205
|
+
console.error("BACKUP FAILED:", err)
|
|
206
|
+
await backupLog(`backup failed: ${detail}`)
|
|
105
207
|
currentBackup = null
|
|
106
208
|
})
|
|
107
209
|
return currentBackup
|
|
@@ -112,7 +214,7 @@ expressApp.get('/backup/doBackup', async (req, res) => {
|
|
|
112
214
|
res.status(200).send('Backup in progress: ' + currentBackup.filename)
|
|
113
215
|
return
|
|
114
216
|
}
|
|
115
|
-
await doBackup()
|
|
217
|
+
await queue.add(() => doBackup())
|
|
116
218
|
res.status(200).send('Creating backup: '+currentBackup.filename)
|
|
117
219
|
})
|
|
118
220
|
|
|
@@ -202,4 +304,4 @@ definition.afterStart(() => {
|
|
|
202
304
|
}, TWENTY_FOUR_HOURS)
|
|
203
305
|
})
|
|
204
306
|
|
|
205
|
-
})
|
|
307
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/backup-service",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.212",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
"author": "Michał Łaszczewski <michal@emikse.com>",
|
|
10
10
|
"license": "BSD-3-Clause",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@live-change/db-client": "^0.9.
|
|
13
|
-
"@live-change/framework": "^0.9.
|
|
12
|
+
"@live-change/db-client": "^0.9.212",
|
|
13
|
+
"@live-change/framework": "^0.9.212",
|
|
14
14
|
"express": "^4.18.2",
|
|
15
15
|
"express-basic-auth": "^1.2.1",
|
|
16
16
|
"fs-extra": "^11.1.1",
|
|
17
|
-
"p-queue": "8.1.
|
|
17
|
+
"p-queue": "^8.1.1",
|
|
18
18
|
"progress-stream": "^2.0.0"
|
|
19
19
|
},
|
|
20
|
-
"gitHead": "
|
|
20
|
+
"gitHead": "40268236c57d5f123c1e8f1815c310464d043d52",
|
|
21
21
|
"type": "module"
|
|
22
22
|
}
|