@jwilleke/ngdpbase-types 5.24.0 → 5.26.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/README.md +2 -2
- package/lib/packages/ngdpbase-types/index.d.ts +6 -2
- package/lib/packages/ngdpbase-types/testing.d.ts +7 -2
- package/lib/packages/ngdpbase-types/testing.js +7 -2
- package/lib/src/http/egressPolicy.js +61 -0
- package/lib/src/http/guardedElasticsearch.d.ts +27 -0
- package/lib/src/http/guardedElasticsearch.js +99 -0
- package/lib/src/managers/AddonsManager.d.ts +119 -2
- package/lib/src/managers/AttachmentManager.d.ts +4 -5
- package/lib/src/managers/AuthManager.d.ts +150 -32
- package/lib/src/managers/BackgroundJobManager.d.ts +22 -12
- package/lib/src/managers/BackupManager.d.ts +779 -0
- package/lib/src/managers/BackupManager.js +2042 -0
- package/lib/src/managers/BaseManager.d.ts +156 -6
- package/lib/src/managers/BaseManager.js +64 -2
- package/lib/src/managers/CommentManager.d.ts +6 -6
- package/lib/src/managers/ConsentManager.d.ts +130 -0
- package/lib/src/managers/DatabaseManager.d.ts +6 -0
- package/lib/src/managers/DatabaseManager.js +25 -15
- package/lib/src/managers/FootnoteManager.d.ts +6 -6
- package/lib/src/managers/OrganizationManager.d.ts +8 -6
- package/lib/src/managers/PageManager.d.ts +16 -0
- package/lib/src/managers/PersonManager.d.ts +8 -6
- package/lib/src/managers/RoleManager.d.ts +6 -8
- package/lib/src/providers/BaseAttachmentProvider.d.ts +4 -4
- package/lib/src/providers/BaseBackupProvider.d.ts +77 -0
- package/lib/src/providers/BaseBackupProvider.js +52 -0
- package/lib/src/providers/BasicAttachmentProvider.d.ts +4 -4
- package/lib/src/providers/FileBackupProvider.d.ts +34 -0
- package/lib/src/providers/FileBackupProvider.js +116 -0
- package/lib/src/providers/FileConsentProvider.d.ts +18 -0
- package/lib/src/providers/SqliteDatabaseProvider.d.ts +2 -2
- package/lib/src/providers/SqliteDatabaseProvider.js +12 -11
- package/lib/src/providers/attachmentFilesBackup.d.ts +5 -4
- package/lib/src/providers/databaseMigrations.js +45 -0
- package/lib/src/security/PolicyDecisionPoint.d.ts +14 -0
- package/lib/src/security/PolicyInformationPoint.d.ts +8 -0
- package/lib/src/types/Consent.d.ts +90 -0
- package/lib/src/types/express.d.ts +6 -0
- package/lib/src/utils/addonHost.d.ts +51 -3
- package/lib/src/utils/addonHost.js +29 -2
- package/lib/src/utils/addonPageSync.d.ts +2 -2
- package/lib/src/utils/auditEventNames.generated.d.ts +20 -0
- package/lib/src/utils/auditEventNames.generated.js +20 -0
- package/lib/src/utils/backupCrypto.js +275 -0
- package/lib/src/utils/backupDocument.d.ts +36 -0
- package/lib/src/utils/backupDocument.js +99 -0
- package/lib/src/utils/browserDescription.d.ts +11 -0
- package/lib/src/utils/directoryBackup.d.ts +48 -48
- package/lib/src/utils/directoryBackup.js +291 -0
- package/lib/src/utils/instanceEnvBackup.d.ts +46 -0
- package/lib/src/utils/instanceEnvBackup.js +120 -0
- package/lib/src/utils/instanceEnvSecret.js +79 -0
- package/lib/src/utils/ipLocation.d.ts +11 -0
- package/lib/src/utils/schedule.js +171 -0
- package/lib/src/utils/secretFileMode.js +78 -0
- package/lib/src/utils/siteRestore.d.ts +13 -16
- package/lib/src/utils/siteRestore.js +33 -6
- package/lib/src/utils/treeArchive.d.ts +2 -0
- package/lib/src/utils/treeArchive.js +242 -0
- package/lib/src/utils/version.js +408 -0
- package/package.json +6 -1
- package/lib/src/utils/administrators.d.ts +0 -21
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
import type { Readable } from 'stream';
|
|
2
|
+
import BaseManager, { type BackupData as BaseBackupData, type BackupFolder, type StagedRestoreAtStart, type StagedSwap } from './BaseManager.js';
|
|
3
|
+
import { type ActorContext } from '../context/ActorContext.js';
|
|
4
|
+
import type { WikiEngine } from '../types/WikiEngine.js';
|
|
5
|
+
import { type Schedule, type ScheduleInput } from '../utils/schedule.js';
|
|
6
|
+
import { type SiteRestoreManifest } from '../utils/siteRestore.js';
|
|
7
|
+
import { type BackupDocument } from '../utils/backupDocument.js';
|
|
8
|
+
import type { JobRunSince } from './BackgroundJobManager.js';
|
|
9
|
+
/**
|
|
10
|
+
* Backup options
|
|
11
|
+
*/
|
|
12
|
+
export interface BackupOptions {
|
|
13
|
+
filename?: string;
|
|
14
|
+
compress?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Leave retention for the next backup (#1723). The backup a restore takes
|
|
17
|
+
* of the live data first sets it, so that backup can never prune the very
|
|
18
|
+
* backup being restored.
|
|
19
|
+
*/
|
|
20
|
+
skipCleanup?: boolean;
|
|
21
|
+
/** Throw when any part of the backup failed, after saying so (#1723): a restore stages nothing without a whole copy of the live data. */
|
|
22
|
+
requireComplete?: boolean;
|
|
23
|
+
/** This is a restore's backup of the live data (#1892 D2); handed to every manager as `safetyBackup`. */
|
|
24
|
+
safetyBackup?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Restore options
|
|
28
|
+
*/
|
|
29
|
+
/** What a restore started from Admin → Backup puts back (#1945): the whole site, or the databases only. */
|
|
30
|
+
export type RestoreScope = 'site' | 'databases';
|
|
31
|
+
export interface RestoreOptions {
|
|
32
|
+
skipValidation?: boolean;
|
|
33
|
+
managerFilter?: string[];
|
|
34
|
+
/** Replace a database restore staged earlier and not applied yet (#1723, D5); refused without it. */
|
|
35
|
+
replaceStaged?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** A backup in the backup directory, as Admin → Backup lists it for download and restore (#1723). */
|
|
38
|
+
export interface StoredBackup extends BackupFileInfo {
|
|
39
|
+
/** The database files beside the document, by name (`application`, `addon-<owner>`). */
|
|
40
|
+
databases: string[];
|
|
41
|
+
/** The file archives beside the document, by part (`attachments`, `roles`, …) (#1876). */
|
|
42
|
+
parts: string[];
|
|
43
|
+
/** Every file beside the document, by filename: what a download carries with it. */
|
|
44
|
+
files: string[];
|
|
45
|
+
/** Whether the document is encrypted under the backup key (#1561): its name ends `.enc`. */
|
|
46
|
+
encrypted: boolean;
|
|
47
|
+
/** The document and every file beside it together, in bytes. */
|
|
48
|
+
totalSize: number;
|
|
49
|
+
}
|
|
50
|
+
/** Backup data structure: the document (`src/utils/backupDocument.ts`). */
|
|
51
|
+
export type BackupData = BackupDocument;
|
|
52
|
+
/**
|
|
53
|
+
* Restore results
|
|
54
|
+
*/
|
|
55
|
+
export interface RestoreResults {
|
|
56
|
+
success: string[];
|
|
57
|
+
failed: Array<{
|
|
58
|
+
manager: string;
|
|
59
|
+
error: string;
|
|
60
|
+
}>;
|
|
61
|
+
skipped: string[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Backup file information
|
|
65
|
+
*/
|
|
66
|
+
export interface BackupFileInfo {
|
|
67
|
+
filename: string;
|
|
68
|
+
path: string;
|
|
69
|
+
size: number;
|
|
70
|
+
created: Date;
|
|
71
|
+
modified: Date;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* BackupManager - Coordinates backup and restore operations across all managers
|
|
75
|
+
*
|
|
76
|
+
* Orchestrates system-wide backup and restore by calling backup()/restore()
|
|
77
|
+
* on all registered managers and aggregating their data into compressed archives.
|
|
78
|
+
*
|
|
79
|
+
* Responsibilities:
|
|
80
|
+
* - Call backup() on all registered managers
|
|
81
|
+
* - Aggregate backup data into a single .gz file
|
|
82
|
+
* - Restore from .gz backup file
|
|
83
|
+
* - Call restore() on all registered managers
|
|
84
|
+
*
|
|
85
|
+
* Architecture:
|
|
86
|
+
* - Each manager implements backup() to return its state
|
|
87
|
+
* - BackupManager collects all states into one object
|
|
88
|
+
* - Serializes to JSON and compresses with gzip
|
|
89
|
+
* - Stores as single .gz file
|
|
90
|
+
*
|
|
91
|
+
* @class BackupManager
|
|
92
|
+
* @extends BaseManager
|
|
93
|
+
*
|
|
94
|
+
* @property {string|null} backupDirectory - Directory for backup files
|
|
95
|
+
* @property {number} maxBackups - Maximum number of backups to retain
|
|
96
|
+
*
|
|
97
|
+
* @see {@link BaseManager} for base functionality and backup() pattern
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* const backupManager = engine.getManager('BackupManager');
|
|
101
|
+
* const backupPath = await backupManager.createBackup(wikiContext.userContext);
|
|
102
|
+
* console.log('Backup created:', backupPath);
|
|
103
|
+
*/
|
|
104
|
+
/** Auto-backup schedule configuration */
|
|
105
|
+
export interface AutoBackupConfig {
|
|
106
|
+
enabled: boolean;
|
|
107
|
+
time: string;
|
|
108
|
+
days: string;
|
|
109
|
+
maxBackups: number;
|
|
110
|
+
directory: string;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* A backup document's filename without `.json.gz` / `.json` (#1723). A
|
|
114
|
+
* manager's own files beside the document are named `<stem>.<name>.db` (a
|
|
115
|
+
* database, #1723) or `<stem>.<part>.tar` (a folder of files, #1876), and are
|
|
116
|
+
* pruned and downloaded with it.
|
|
117
|
+
*/
|
|
118
|
+
export declare function backupStem(filename: string): string;
|
|
119
|
+
/** Whether `filename` is one of the files a manager wrote beside the document named `documentFilename` (#1723); a file archive may be encrypted, `.tar.enc` (#1561). */
|
|
120
|
+
export declare function isCompanionOf(filename: string, documentFilename: string): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* What no backup holds, so no restore brings back (#1876), in the words
|
|
123
|
+
* admins read: these stay as they are on the site. The backup-restore guide
|
|
124
|
+
* and the Backup help page list the same.
|
|
125
|
+
*/
|
|
126
|
+
export declare const NEVER_IN_A_BACKUP: readonly string[];
|
|
127
|
+
/** The backup document entry, and the site restore part, an add-on's files are recorded under (#1985). */
|
|
128
|
+
export declare function addonEntryName(slug: string): string;
|
|
129
|
+
/**
|
|
130
|
+
* Put a staged restore of a manager's declared folders and files in place,
|
|
131
|
+
* at start, before it reads them (#1985). BackupManager's step: a manager
|
|
132
|
+
* reaches it through `BaseManager.initialize()`, an add-on's folders through
|
|
133
|
+
* AddonsManager before the add-on's `register()`. A module function, not a
|
|
134
|
+
* method, because most managers start before BackupManager does; it needs
|
|
135
|
+
* only the configuration, for the site restore manifest.
|
|
136
|
+
*/
|
|
137
|
+
export declare function applyStagedPartsAtStart(engine: WikiEngine, manager: string, declared: BackupFolder[]): StagedRestoreAtStart | null;
|
|
138
|
+
/** How a manager whose restore is staged (`restoreIsStaged()`) puts its part in place at start (#1986). */
|
|
139
|
+
export interface StagedPartAtStartOptions {
|
|
140
|
+
/** Whether this manager may replace that live path: a manifest naming any other is refused, whatever it says. */
|
|
141
|
+
allowed: (swap: StagedSwap) => boolean;
|
|
142
|
+
/** The live paths of this manager's that may have a staged copy beside them: one no committed restore names (an interrupted staging) is set aside. */
|
|
143
|
+
mayBeStaged?: () => string[];
|
|
144
|
+
/**
|
|
145
|
+
* Put one swap in place yourself, instead of the rename: for a part whose
|
|
146
|
+
* live path is not a file or folder of its own (the audit files share the
|
|
147
|
+
* log folder). Move the live content to `kept` and the staged one in;
|
|
148
|
+
* throw to refuse.
|
|
149
|
+
*/
|
|
150
|
+
put?: (swap: StagedSwap, kept: string) => void;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Put `manager`'s part of a staged site restore in place, at start, before
|
|
154
|
+
* it reads its data (#1876, #1986): when the whole restore is there to
|
|
155
|
+
* apply, each live path steps aside as `<live>.pre-restore` and the staged
|
|
156
|
+
* copy takes its name; otherwise its staged files are set aside as
|
|
157
|
+
* `*.incomplete`. The part is marked in BackupManager's record either way.
|
|
158
|
+
* For a manager whose restore is staged by its own `restore()` (the
|
|
159
|
+
* attachments, the audit log); declared folders and files go through
|
|
160
|
+
* `applyStagedPartsAtStart`. A module function: most managers start before
|
|
161
|
+
* BackupManager. Null when there was nothing to do.
|
|
162
|
+
*/
|
|
163
|
+
export declare function applyStagedPartAtStart(engine: WikiEngine, manager: string, options: StagedPartAtStartOptions): StagedRestoreAtStart | null;
|
|
164
|
+
/**
|
|
165
|
+
* BackupManager's answer, at start, for a manager that applies a staged
|
|
166
|
+
* restore of its own kind (the databases, #1723): whether its part of a
|
|
167
|
+
* staged site restore is to go in now (#1876, #1986).
|
|
168
|
+
*
|
|
169
|
+
* - `none` — no site restore names it: the manager does as it would alone.
|
|
170
|
+
* - `apply` — the whole restore is there: put it in, then `settle` it.
|
|
171
|
+
* - `set-aside` — it isn't (`reason`): set the staged files aside, then `settle` it.
|
|
172
|
+
*/
|
|
173
|
+
export interface SiteRestorePartAtStart {
|
|
174
|
+
decision: 'none' | 'apply' | 'set-aside';
|
|
175
|
+
reason?: string;
|
|
176
|
+
/** Record what became of the part. Nothing to record for `none`, or a set-aside with no part of this manager's. */
|
|
177
|
+
settle(status: 'applied' | 'set-aside', note?: string): void;
|
|
178
|
+
}
|
|
179
|
+
/** BackupManager's start-time gate on `manager`'s part of a staged site restore (#1986): see {@link SiteRestorePartAtStart}. */
|
|
180
|
+
export declare function siteRestorePartAtStart(engine: WikiEngine, manager: string): SiteRestorePartAtStart;
|
|
181
|
+
/** A manager's data as the "not restored" notification names it. */
|
|
182
|
+
export declare function restoreLabel(manager: string): string;
|
|
183
|
+
/**
|
|
184
|
+
* What a staged restore will not bring back, written when it is staged (#1876)
|
|
185
|
+
* and read once it has been applied, for the critical notification: kept by
|
|
186
|
+
* BackupManager beside the site restore manifest, as `<manifest>.report.json`.
|
|
187
|
+
*/
|
|
188
|
+
export interface RestoreReport {
|
|
189
|
+
version: 1;
|
|
190
|
+
source: string;
|
|
191
|
+
/** When the backup was taken. */
|
|
192
|
+
backup: string;
|
|
193
|
+
/** The backup's parts that were not staged, by manager. */
|
|
194
|
+
notRestored: string[];
|
|
195
|
+
/** The job runs since the backup, read from the live job state when the restore was staged. */
|
|
196
|
+
jobRuns: JobRunSince[];
|
|
197
|
+
/** Who started the restore. */
|
|
198
|
+
stagedBy?: string;
|
|
199
|
+
}
|
|
200
|
+
/** BackupManager's fault, and the dashboard banner, while there is no backup key (#1561, #1912). */
|
|
201
|
+
export declare const BACKUP_KEY_MISSING = "NGDPBASE_BACKUP_KEY is not set, so backups are NOT encrypted: the document (password hashes, settings with their secrets) and the file archives beside it are in the clear, and the credentials store, the databases and the instance .env are left out";
|
|
202
|
+
/** The title of the one notification about it. */
|
|
203
|
+
export declare const BACKUP_KEY_MISSING_TITLE = "Backups are not encrypted";
|
|
204
|
+
/**
|
|
205
|
+
* The config key that records an admin's confirmation that the backup key is
|
|
206
|
+
* saved off the server (#1876): the fingerprint of the key they saved. A
|
|
207
|
+
* different key (a new one made, or one replaced) asks again.
|
|
208
|
+
*/
|
|
209
|
+
export declare const BACKUP_KEY_SAVED_KEY = "ngdpbase.backup.key-saved-fingerprint";
|
|
210
|
+
/** The banner while no admin has confirmed the backup key is saved off the server (#1876). */
|
|
211
|
+
export declare function backupKeyNotSaved(fingerprint: string): string;
|
|
212
|
+
/**
|
|
213
|
+
* The config key that records the names (never the values) the instance
|
|
214
|
+
* `.env` set when an admin last confirmed it is saved off the server (#1976),
|
|
215
|
+
* sorted. Null until the first check records the baseline. The names, not
|
|
216
|
+
* only their fingerprint, so the reminder can say which keys were added or
|
|
217
|
+
* removed.
|
|
218
|
+
*/
|
|
219
|
+
export declare const ENV_KEYS_SAVED_KEY = "ngdpbase.backup.env-keys-saved";
|
|
220
|
+
/** The title of the one notification per change of the `.env` key set (#1976). */
|
|
221
|
+
export declare const ENV_KEYS_CHANGED_TITLE = "Save a copy of .env";
|
|
222
|
+
/** The banner while the instance `.env` sets other names than the ones an admin last confirmed saved (#1976): names only, never a value. */
|
|
223
|
+
export declare function envKeysChanged(change: {
|
|
224
|
+
added: readonly string[];
|
|
225
|
+
removed: readonly string[];
|
|
226
|
+
}): string;
|
|
227
|
+
/** What Admin → Backup shows about the instance `.env` key set (#1976): names only, never a value. */
|
|
228
|
+
export interface EnvKeysInfo {
|
|
229
|
+
/** The names the file sets now, sorted. */
|
|
230
|
+
names: string[];
|
|
231
|
+
/** Their fingerprint, posted back with the confirmation so a stale page is refused. */
|
|
232
|
+
fingerprint: string;
|
|
233
|
+
/** Whether a set has been recorded at all: false only before the first check. */
|
|
234
|
+
recorded: boolean;
|
|
235
|
+
/** Names set now that were not when an admin last confirmed. */
|
|
236
|
+
added: string[];
|
|
237
|
+
/** Names an admin confirmed that the file no longer sets. */
|
|
238
|
+
removed: string[];
|
|
239
|
+
/** The file holds this site's current backup key, so a saved copy is a saved key too. */
|
|
240
|
+
holdsBackupKey: boolean;
|
|
241
|
+
}
|
|
242
|
+
/** What Admin → Backup shows about the backup key: never the key itself. */
|
|
243
|
+
export interface BackupKeyInfo {
|
|
244
|
+
set: boolean;
|
|
245
|
+
fingerprint: string | null;
|
|
246
|
+
/** An admin confirmed this key is saved off the server. */
|
|
247
|
+
saved: boolean;
|
|
248
|
+
}
|
|
249
|
+
/** The scheduled job automatic backups run as (#1720). */
|
|
250
|
+
export declare const AUTO_BACKUP_JOB_ID = "backup.auto";
|
|
251
|
+
/** The routine check that backups are still happening (#1563). */
|
|
252
|
+
export declare const BACKUP_HEALTH_JOB_ID = "backup.health";
|
|
253
|
+
/** The title of the notification sent when backups stop working (#1563): a backup failed, or none is recent enough. */
|
|
254
|
+
export declare const BACKUPS_NOT_WORKING_TITLE = "Backups are not working";
|
|
255
|
+
/** A backup that failed (#1563), as kept in the health record until a backup succeeds. */
|
|
256
|
+
export interface BackupFailure {
|
|
257
|
+
/** When it failed, ISO 8601. */
|
|
258
|
+
at: string;
|
|
259
|
+
/** Why, in words an admin can act on. */
|
|
260
|
+
reason: string;
|
|
261
|
+
/** The document that was written anyway (a backup with a part missing), so it is not counted as good. */
|
|
262
|
+
filename?: string;
|
|
263
|
+
}
|
|
264
|
+
/** Whether backups are working, as Admin → Backup and the /admin dashboard show it (#1563). */
|
|
265
|
+
export interface BackupHealth {
|
|
266
|
+
/** The newest backup in the backup directory that is not a failed one: when, how big (with the files beside it), where. */
|
|
267
|
+
lastGood: {
|
|
268
|
+
filename: string;
|
|
269
|
+
at: string;
|
|
270
|
+
size: number;
|
|
271
|
+
location: string;
|
|
272
|
+
} | null;
|
|
273
|
+
/** Automatic backups are on and scheduled. */
|
|
274
|
+
autoBackup: boolean;
|
|
275
|
+
/** The next automatic backup, ISO 8601, or null. */
|
|
276
|
+
nextRun: string | null;
|
|
277
|
+
/** Free bytes at the backup destination, or null when it can't be told. */
|
|
278
|
+
freeBytes: number | null;
|
|
279
|
+
/** The backup that failed, until one succeeds. */
|
|
280
|
+
failure: BackupFailure | null;
|
|
281
|
+
/** How long the site may go without a good backup before that is an outage, in ms. */
|
|
282
|
+
staleAfterMs: number;
|
|
283
|
+
/** No good backup within `staleAfterMs`, or none at all. */
|
|
284
|
+
stale: boolean;
|
|
285
|
+
/** The outage, in one line, while backups are not working; null when they are. */
|
|
286
|
+
fault: string | null;
|
|
287
|
+
}
|
|
288
|
+
/** A number of bytes as admins read it. */
|
|
289
|
+
export declare function formatBytes(bytes: number): string;
|
|
290
|
+
/** Why a backup could not be written, in words an admin can act on: the system's error codes named (#1563). */
|
|
291
|
+
export declare function describeBackupError(err: unknown): string;
|
|
292
|
+
/**
|
|
293
|
+
* The longest gap between two runs of a schedule (#1563): what "the
|
|
294
|
+
* auto-backup interval" is for a schedule that is not evenly spaced (Monday,
|
|
295
|
+
* Wednesday and Friday is three days at its longest; monthly is 31). Counted
|
|
296
|
+
* over the next year's runs, at most 400 of them.
|
|
297
|
+
*/
|
|
298
|
+
export declare function longestGapMs(schedule: Schedule, from: Date): number;
|
|
299
|
+
/**
|
|
300
|
+
* The automatic-backup settings as a schedule (#1720): `ngdpbase.backup.auto-backup-time`
|
|
301
|
+
* (HH:MM) and `ngdpbase.backup.auto-backup-days` — `daily`, `monthly` (the 1st),
|
|
302
|
+
* `weekdays` (Monday to Friday), or day names such as `Mon,Wed,Fri`.
|
|
303
|
+
*
|
|
304
|
+
* In `timeZone`: the server's own, because the timer this replaces compared
|
|
305
|
+
* the server's local clock, so an existing setting keeps its time.
|
|
306
|
+
*
|
|
307
|
+
* Throws, naming the setting, for a time or a day it does not understand. The
|
|
308
|
+
* timer this replaces matched neither, so such a setting never backed up, and
|
|
309
|
+
* said nothing.
|
|
310
|
+
*/
|
|
311
|
+
export declare function autoBackupSchedule(time: string, days: string, timeZone: string): ScheduleInput;
|
|
312
|
+
/**
|
|
313
|
+
* The text of the critical "not restored" notification (#1876): what the
|
|
314
|
+
* restore put back, what it did not, and per job the runs since the backup —
|
|
315
|
+
* its name, when it last ran and how that ended — newest first.
|
|
316
|
+
*/
|
|
317
|
+
export declare function describeNotRestored(input: {
|
|
318
|
+
source: string;
|
|
319
|
+
backup: string;
|
|
320
|
+
applied: string[];
|
|
321
|
+
notRestored: string[];
|
|
322
|
+
jobRuns: JobRunSince[];
|
|
323
|
+
nameOf: (jobId: string) => string;
|
|
324
|
+
missingReport?: boolean;
|
|
325
|
+
}): string;
|
|
326
|
+
declare class BackupManager extends BaseManager {
|
|
327
|
+
private backupDirectory;
|
|
328
|
+
private maxBackups;
|
|
329
|
+
private autoBackupEnabled;
|
|
330
|
+
private autoBackupTime;
|
|
331
|
+
private autoBackupDays;
|
|
332
|
+
private provider;
|
|
333
|
+
private providerClass;
|
|
334
|
+
/** A restore started from Admin → Backup, while it runs (#1723). */
|
|
335
|
+
private restoreRunning;
|
|
336
|
+
/** The outage this manager set because backups are not working (#1563), so only that one is cleared when they work again. */
|
|
337
|
+
private healthFault;
|
|
338
|
+
/**
|
|
339
|
+
* The end of the queue of work that reads or changes the backup directory
|
|
340
|
+
* one at a time (#1563): a backup with its pruning, a restore's safety
|
|
341
|
+
* backup and staging, and the routine health check (`exclusive`).
|
|
342
|
+
*/
|
|
343
|
+
private exclusiveTail;
|
|
344
|
+
/**
|
|
345
|
+
* Creates a new BackupManager instance
|
|
346
|
+
*
|
|
347
|
+
* @constructor
|
|
348
|
+
* @param {any} engine - The wiki engine instance
|
|
349
|
+
*/
|
|
350
|
+
constructor(engine: WikiEngine);
|
|
351
|
+
/** Normalize a config provider name to its PascalCase class name. */
|
|
352
|
+
private normalizeProviderName;
|
|
353
|
+
/**
|
|
354
|
+
* Load the configured backup storage provider (#170). Mirrors the
|
|
355
|
+
* dynamic-import + fallback pattern used by AuditManager / SearchManager.
|
|
356
|
+
* Falls back to FileBackupProvider on any load error so backups never
|
|
357
|
+
* silently lose their storage target.
|
|
358
|
+
*/
|
|
359
|
+
private loadProvider;
|
|
360
|
+
/**
|
|
361
|
+
* Initialize BackupManager
|
|
362
|
+
*
|
|
363
|
+
* @async
|
|
364
|
+
* @param {Record<string, unknown>} [config={}] - Configuration object (unused, reads from ConfigurationManager)
|
|
365
|
+
* @returns {Promise<void>}
|
|
366
|
+
* @throws {Error} If ConfigurationManager is not available
|
|
367
|
+
*/
|
|
368
|
+
initialize(config?: Record<string, unknown>): Promise<void>;
|
|
369
|
+
/**
|
|
370
|
+
* Backup BackupManager's own state (conforms to BaseManager interface)
|
|
371
|
+
*
|
|
372
|
+
* @returns {Promise<BaseBackupData>} Backup data for this manager
|
|
373
|
+
*/
|
|
374
|
+
backup(): Promise<BaseBackupData>;
|
|
375
|
+
/**
|
|
376
|
+
* Restore BackupManager's own state (conforms to BaseManager interface)
|
|
377
|
+
*
|
|
378
|
+
* @param {BaseBackupData} backupData - Backup data from backup()
|
|
379
|
+
* @returns {Promise<void>}
|
|
380
|
+
*/
|
|
381
|
+
restoreState(backupData: BaseBackupData): Promise<void>;
|
|
382
|
+
/**
|
|
383
|
+
* Perform a complete backup of all managers to a file
|
|
384
|
+
*
|
|
385
|
+
* Process:
|
|
386
|
+
* 1. Get all registered managers from engine
|
|
387
|
+
* 2. Call backup() on each manager
|
|
388
|
+
* 3. Aggregate all backup data
|
|
389
|
+
* 4. Compress to .gz file
|
|
390
|
+
* 5. Save with timestamp
|
|
391
|
+
* 6. Clean up old backups
|
|
392
|
+
*
|
|
393
|
+
* @param {BackupOptions} options - Backup options
|
|
394
|
+
* @param {string} options.filename - Custom filename (optional)
|
|
395
|
+
* @param {boolean} options.compress - Whether to compress (default: true)
|
|
396
|
+
* @param ctx - Who asked (#1179): the request's subject, or the schedule's JobContext. First and mandatory — a full copy of the instance is made on someone's behalf.
|
|
397
|
+
* @returns {Promise<string>} Path to created backup file
|
|
398
|
+
*/
|
|
399
|
+
createBackup(ctx: ActorContext, options?: BackupOptions): Promise<string>;
|
|
400
|
+
/**
|
|
401
|
+
* One at a time (#1563): `work` starts once everything queued before it
|
|
402
|
+
* has finished, whether that succeeded or failed, and is never run beside
|
|
403
|
+
* it. A second backup asked for while one runs waits for it and is then
|
|
404
|
+
* taken in its own right: its own document, its own options, its own
|
|
405
|
+
* result. So a backup's pruning never deletes files another backup, a
|
|
406
|
+
* restore or the health check is reading. In-process only: one server
|
|
407
|
+
* process owns a backup directory.
|
|
408
|
+
*/
|
|
409
|
+
private exclusive;
|
|
410
|
+
/** `createBackup` itself, for a caller already holding the queue (`exclusive`): a restore's safety backup. */
|
|
411
|
+
private createBackupNow;
|
|
412
|
+
/**
|
|
413
|
+
* The instance `.env` beside the document (#1876): `<stem>.env.tar.enc`,
|
|
414
|
+
* only ever encrypted, and never with the backup key's own line. Without
|
|
415
|
+
* the key it is left out (`LEFT_OUT_FOR_NO_KEY`). BackupManager owns this
|
|
416
|
+
* part: the file belongs to no manager, and the backup key is this one's.
|
|
417
|
+
*/
|
|
418
|
+
private backupInstanceEnv;
|
|
419
|
+
/** `<FAST_STORAGE>/.env`, or null when the instance data folder is not known. */
|
|
420
|
+
private instanceEnvFile;
|
|
421
|
+
/** The files the managers wrote beside one backup document (#1723). */
|
|
422
|
+
private companionsOf;
|
|
423
|
+
/** Remove the files the managers wrote beside one backup document (#1723), except those in `keep`. */
|
|
424
|
+
private removeCompanions;
|
|
425
|
+
/**
|
|
426
|
+
* Without `NGDPBASE_BACKUP_KEY` backups are still taken, unencrypted, with
|
|
427
|
+
* the credentials store and the databases left out (#1561, #1876). That is
|
|
428
|
+
* said twice, never once per backup (operator, #1912): BackupManager is
|
|
429
|
+
* degraded for as long as the key is missing — the persistent banner on the
|
|
430
|
+
* dashboard — and admins get ONE notification the first time. It is
|
|
431
|
+
* deduplicated the way other one-time notices are (`pageMetadataMissing`):
|
|
432
|
+
* by its title among the stored notifications, which persist across
|
|
433
|
+
* restarts and keep a dismissed one. With the key set the fault clears.
|
|
434
|
+
* Read at start and at each backup.
|
|
435
|
+
*/
|
|
436
|
+
private checkBackupKey;
|
|
437
|
+
/**
|
|
438
|
+
* Whether a notification with this title (and message, when given) is
|
|
439
|
+
* among the stored ones, which persist across restarts and keep a dismissed
|
|
440
|
+
* one: how a one-time notice is deduplicated (`pageMetadataMissing`).
|
|
441
|
+
*/
|
|
442
|
+
private alreadyNotified;
|
|
443
|
+
/**
|
|
444
|
+
* The instance `.env` key set against the one an admin last confirmed saved
|
|
445
|
+
* off the server (#1976): the reminder while they differ, else null. Names
|
|
446
|
+
* only — a value is never kept, logged or shown.
|
|
447
|
+
*
|
|
448
|
+
* The first check on a site with nothing recorded (a new site, or one
|
|
449
|
+
* upgraded to this release) records the names it finds as the baseline,
|
|
450
|
+
* silently: an upgraded site's keys are already in every encrypted backup,
|
|
451
|
+
* and a new site is already asked to save its backup key, so asking again at
|
|
452
|
+
* that moment would be noise. Every change after it asks. One notification
|
|
453
|
+
* per change, deduplicated by its title and words among the stored
|
|
454
|
+
* notifications, so a restart never repeats it.
|
|
455
|
+
*/
|
|
456
|
+
private checkInstanceEnvKeys;
|
|
457
|
+
/** The instance `.env` key set as Admin → Backup shows it (#1976), or null when the site has no instance data folder or the file can't be read. Never a value. */
|
|
458
|
+
envKeysInfo(): EnvKeysInfo | null;
|
|
459
|
+
/** The backup key as Admin → Backup shows it: set or not, its fingerprint, and whether an admin confirmed it is saved. Never the key. */
|
|
460
|
+
backupKeyInfo(): BackupKeyInfo;
|
|
461
|
+
/**
|
|
462
|
+
* The backup key itself, for the one page that asks an admin to copy it
|
|
463
|
+
* off the server (#1876). The route asks `config-manage`, with its fresh
|
|
464
|
+
* sign-in; nothing else shows, logs or returns it.
|
|
465
|
+
*/
|
|
466
|
+
revealBackupKey(): string | null;
|
|
467
|
+
/**
|
|
468
|
+
* The instance `.env` exactly as it is on disk, backup key line included,
|
|
469
|
+
* for an admin to keep a copy off the server (#1938). The route asks
|
|
470
|
+
* `config-manage`, with its fresh sign-in. Recorded as `secret-reveal`:
|
|
471
|
+
* the file, never its contents. Null when the site has no instance `.env`.
|
|
472
|
+
*/
|
|
473
|
+
revealInstanceEnv(ctx: ActorContext): Promise<Buffer | null>;
|
|
474
|
+
/**
|
|
475
|
+
* An admin confirms the backup key with this fingerprint is saved off the
|
|
476
|
+
* server (#1876). Recorded in the configuration (`ngdpbase.backup.key-saved-fingerprint`,
|
|
477
|
+
* written through ConfigurationManager, which records the change), so it
|
|
478
|
+
* survives restarts; a different key asks again. Refused when the
|
|
479
|
+
* fingerprint is not the current key's: the page they read was stale.
|
|
480
|
+
*
|
|
481
|
+
* With `envFingerprint` (#1976) the admin confirms the whole `.env` is
|
|
482
|
+
* saved, the one confirmation for both reminders: the names it sets are
|
|
483
|
+
* recorded (`ngdpbase.backup.env-keys-saved`), and so is the backup key
|
|
484
|
+
* when the file holds it. Refused when the names changed since the page
|
|
485
|
+
* was loaded.
|
|
486
|
+
*/
|
|
487
|
+
confirmBackupKeySaved(ctx: ActorContext, fingerprint: string, envFingerprint?: string): Promise<void>;
|
|
488
|
+
private configManager;
|
|
489
|
+
private setting;
|
|
490
|
+
/** `ngdpbase.backup.health-file`: the failure kept across restarts. */
|
|
491
|
+
private healthFile;
|
|
492
|
+
private readHealthRecord;
|
|
493
|
+
private writeHealthRecord;
|
|
494
|
+
/**
|
|
495
|
+
* A backup failed (#1563): kept until one succeeds, across restarts, and
|
|
496
|
+
* said once — admins get one "Backups are not working" notification per
|
|
497
|
+
* reason, not one per failed attempt. `notify: false` where another
|
|
498
|
+
* notification already said it ("A backup is incomplete").
|
|
499
|
+
*/
|
|
500
|
+
private recordBackupFailure;
|
|
501
|
+
/** A backup succeeded (#1563): the failure kept since the last one is cleared. */
|
|
502
|
+
private clearBackupFailure;
|
|
503
|
+
/** The newest backup in the backup directory that is not the failed one: read from the directory, never remembered. */
|
|
504
|
+
private lastGoodBackup;
|
|
505
|
+
/** Automatic backups as they run (#1563): on only when scheduled and not paused, with the schedule in force and the next run. */
|
|
506
|
+
private autoBackupRun;
|
|
507
|
+
/**
|
|
508
|
+
* Whether backups are working (#1563), from what is on disk: the newest
|
|
509
|
+
* good backup in the backup directory, the failure kept in the health
|
|
510
|
+
* record, the automatic-backup schedule, and free space at the destination.
|
|
511
|
+
*
|
|
512
|
+
* No good backup within the window is an outage. The window is
|
|
513
|
+
* `ngdpbase.backup.stale-interval-multiplier` (default 2) times the longest
|
|
514
|
+
* gap between automatic backups when they are on, and
|
|
515
|
+
* `ngdpbase.backup.stale-fallback-days` (default 7) when they are off.
|
|
516
|
+
*/
|
|
517
|
+
getBackupHealth(): Promise<BackupHealth>;
|
|
518
|
+
/**
|
|
519
|
+
* Bring the dashboard in line with whether backups work (#1563): an outage
|
|
520
|
+
* (`markDegraded`) while a backup has failed or none is recent enough,
|
|
521
|
+
* cleared — only that one — once they work, then the backup key's reminder
|
|
522
|
+
* (`checkBackupKey`), which never hides an outage. A stretch with no recent
|
|
523
|
+
* backup notifies admins once, kept across restarts. Run at start, on the
|
|
524
|
+
* `backup.health` timer, and after every backup.
|
|
525
|
+
*/
|
|
526
|
+
refreshBackupHealth(): Promise<BackupHealth | null>;
|
|
527
|
+
/** `refreshBackupHealth` itself, for a caller already holding the queue: a backup that just ended. */
|
|
528
|
+
private refreshBackupHealthNow;
|
|
529
|
+
/** One notification per stretch without a recent good backup (#1563), kept across restarts by the health record. */
|
|
530
|
+
private notifyStaleOnce;
|
|
531
|
+
/**
|
|
532
|
+
* The routine check (#1563): `backup.health`, every
|
|
533
|
+
* `ngdpbase.backup.health-check-interval` ms (default one hour), as the
|
|
534
|
+
* system principal (BackgroundJobManager's routine maintenance). It is how
|
|
535
|
+
* a backup that never comes is noticed.
|
|
536
|
+
*/
|
|
537
|
+
private registerHealthCheck;
|
|
538
|
+
/**
|
|
539
|
+
* Refuse a backup there is no room for (#1563), before anything is
|
|
540
|
+
* written: free space at the destination against the last backup's size
|
|
541
|
+
* (the document and every file beside it) times
|
|
542
|
+
* `ngdpbase.backup.space-margin` (default 1.5). Skipped when there is no
|
|
543
|
+
* earlier backup or the free space can't be told; a write that then runs
|
|
544
|
+
* out still fails, and says so.
|
|
545
|
+
*/
|
|
546
|
+
private checkFreeSpace;
|
|
547
|
+
/**
|
|
548
|
+
* Read a backup just written back (#1563): decrypt it (every record's GCM
|
|
549
|
+
* tag), decompress it, parse it and check its shape, on a worker thread.
|
|
550
|
+
* A document that does not read back is removed — it is not a backup — and
|
|
551
|
+
* the backup fails, naming why.
|
|
552
|
+
*/
|
|
553
|
+
private verifyWrittenBackup;
|
|
554
|
+
private notifyAdmins;
|
|
555
|
+
/**
|
|
556
|
+
* Restore all managers from a backup file
|
|
557
|
+
*
|
|
558
|
+
* Process:
|
|
559
|
+
* 1. Read and decompress backup file
|
|
560
|
+
* 2. Parse JSON data
|
|
561
|
+
* 3. Validate backup structure
|
|
562
|
+
* 4. Call restore() on each manager with its data
|
|
563
|
+
*
|
|
564
|
+
* The databases (#1723) go first, and are staged for the next start rather
|
|
565
|
+
* than restored in place: a fresh backup of the live data is taken, then
|
|
566
|
+
* DatabaseManager stages every database in the backup. If either fails,
|
|
567
|
+
* nothing at all is restored.
|
|
568
|
+
*
|
|
569
|
+
* @param ctx - Who asked (#1179): a restore takes a backup and replaces the instance's data on someone's behalf.
|
|
570
|
+
* @param {string} backupPath - Path to backup file
|
|
571
|
+
* @param {RestoreOptions} options - Restore options
|
|
572
|
+
* @param {boolean} options.skipValidation - Skip validation (default: false)
|
|
573
|
+
* @param {string[]} options.managerFilter - Only restore specific managers
|
|
574
|
+
* @returns {Promise<RestoreResults>} Restore results
|
|
575
|
+
*/
|
|
576
|
+
restoreFromFile(ctx: ActorContext, backupPath: string, options?: RestoreOptions): Promise<RestoreResults>;
|
|
577
|
+
private restoreFromFileNow;
|
|
578
|
+
/** Whether this site's manager of that name stages its restore (#1876), or its entry is declared folders (#1985). */
|
|
579
|
+
private restoresStaged;
|
|
580
|
+
/**
|
|
581
|
+
* Each loaded add-on's folders (#1985): its `dataPath`, which counts for it
|
|
582
|
+
* whatever it declares, and any further folders its module declares; under
|
|
583
|
+
* the entry its files have always had (`addonEntryName`). For a backup, a
|
|
584
|
+
* `dataPath` that does not exist is left out: that add-on keeps no files.
|
|
585
|
+
*/
|
|
586
|
+
private addonParts;
|
|
587
|
+
/** What a manager declares, and an add-on's folders recorded under the same entry (#1985). */
|
|
588
|
+
private declaredFor;
|
|
589
|
+
/** Every entry with declared folders or files, in registration order, add-ons' after (#1985). */
|
|
590
|
+
private declaredParts;
|
|
591
|
+
/**
|
|
592
|
+
* Put a staged restore of an add-on's folders in place before its
|
|
593
|
+
* `register()` runs, so before any of its managers reads them (#1985).
|
|
594
|
+
* AddonsManager calls it as it loads each add-on.
|
|
595
|
+
*/
|
|
596
|
+
applyAddonPartsAtStart(slug: string, folders: BackupFolder[]): StagedRestoreAtStart | null;
|
|
597
|
+
/** The site restore manifest, as configured (#1876). */
|
|
598
|
+
private siteRestorePath;
|
|
599
|
+
/** The site restore staged and waiting for the next start, or null (#1876). Throws for a record that can't be read. */
|
|
600
|
+
getStagedSiteRestore(): SiteRestoreManifest | null;
|
|
601
|
+
/** A databases-only restore is refused while a site restore is staged: it would take the site restore's databases away from it (#1876). */
|
|
602
|
+
private refuseIfSiteRestoreStaged;
|
|
603
|
+
/**
|
|
604
|
+
* Stage a whole-site restore (#1876): every manager in `names` stages its
|
|
605
|
+
* part for the next start, after a complete backup of the live data, under
|
|
606
|
+
* one site restore manifest. All or nothing: a part that can't be staged
|
|
607
|
+
* discards every part staged before it, and the live data is never
|
|
608
|
+
* touched. The next start applies every part or none
|
|
609
|
+
* (`src/utils/siteRestore.ts`).
|
|
610
|
+
*/
|
|
611
|
+
private stageSiteRestore;
|
|
612
|
+
/**
|
|
613
|
+
* Throw away a staged site restore before the restart applies it (#1876):
|
|
614
|
+
* every staged file and the record are removed, the live data is
|
|
615
|
+
* untouched. Recorded first (`backup-restore-cancel`, refuse).
|
|
616
|
+
*/
|
|
617
|
+
cancelStagedSiteRestore(ctx: ActorContext): Promise<SiteRestoreManifest>;
|
|
618
|
+
/** Where this manager keeps the report of a staged restore: beside the site restore manifest (#1876). */
|
|
619
|
+
private restoreReportPath;
|
|
620
|
+
private removeRestoreReport;
|
|
621
|
+
/**
|
|
622
|
+
* Record, when a restore is staged, what it will not bring back (#1876):
|
|
623
|
+
* the parts of the backup not in it, and the job runs since the backup,
|
|
624
|
+
* read from the live job state now, before the restore replaces it.
|
|
625
|
+
*/
|
|
626
|
+
private writeRestoreReport;
|
|
627
|
+
/**
|
|
628
|
+
* Tell admins what this start did with a staged restore (#1876). Called
|
|
629
|
+
* once every manager and add-on has started (`WikiEngine.initializeAddons`):
|
|
630
|
+
* each owner applied (or set aside) its part as it started, add-ons last.
|
|
631
|
+
* After any restore that was applied, a critical notification lists what it
|
|
632
|
+
* did not bring back, and the job runs between the backup and the restore.
|
|
633
|
+
*/
|
|
634
|
+
reportRestoreAtBoot(): Promise<void>;
|
|
635
|
+
/**
|
|
636
|
+
* The critical notification after a restore was applied (#1876): every
|
|
637
|
+
* part of the site it did not bring back, and the job runs between the
|
|
638
|
+
* backup and the restore (name, last run, result), which are not in the
|
|
639
|
+
* restored job history. Jobs are never run again because of a restore.
|
|
640
|
+
*/
|
|
641
|
+
private reportNotRestored;
|
|
642
|
+
/** The report written when the restore was staged, or null. */
|
|
643
|
+
private readRestoreReport;
|
|
644
|
+
/**
|
|
645
|
+
* `backup-restore-applied` (#1876, operator-approved): written at the start
|
|
646
|
+
* that applied a restore, so into the restored audit log — which otherwise
|
|
647
|
+
* would not say a restore happened (the `backup-restore` record of the
|
|
648
|
+
* staging is in the log it replaced). Names the backup, when it was taken,
|
|
649
|
+
* who started the restore and what was put back. on-failure: continue — the
|
|
650
|
+
* restore is already in place.
|
|
651
|
+
*/
|
|
652
|
+
private recordRestoreApplied;
|
|
653
|
+
/** Tell admins what the start did with a staged site restore (#1876). */
|
|
654
|
+
private reportSiteRestoreOutcome;
|
|
655
|
+
/**
|
|
656
|
+
* The backups in the backup directory with the database files beside each
|
|
657
|
+
* (#1723): what Admin → Backup offers to download and restore. Read from
|
|
658
|
+
* the file names, so no document is opened to list them.
|
|
659
|
+
*/
|
|
660
|
+
listStoredBackups(): Promise<StoredBackup[]>;
|
|
661
|
+
/** A backup in the backup directory by its bare filename, or null: never a path the caller made up. */
|
|
662
|
+
findStoredBackup(filename: string): Promise<StoredBackup | null>;
|
|
663
|
+
/**
|
|
664
|
+
* One backup as a `.tar` stream (#1723, D2): the document and every file
|
|
665
|
+
* beside it — the databases, and the archives of attachment files, role
|
|
666
|
+
* membership, persons, organizations, footnotes and comments (#1876) —
|
|
667
|
+
* read from disk as the stream is consumed, never built in memory. With
|
|
668
|
+
* the backup key set every file in it is encrypted under that key, which
|
|
669
|
+
* is never in the archive (#1723, #1561).
|
|
670
|
+
*/
|
|
671
|
+
backupArchive(filename: string): Promise<{
|
|
672
|
+
name: string;
|
|
673
|
+
stream: Readable;
|
|
674
|
+
}>;
|
|
675
|
+
/** Whether a restore started from Admin → Backup is still taking its fresh backup or staging. */
|
|
676
|
+
isRestoreRunning(): boolean;
|
|
677
|
+
/** The managers on this site whose restore is staged (#1876), in registration order: what a site restore stages. */
|
|
678
|
+
private stagedManagerNames;
|
|
679
|
+
/**
|
|
680
|
+
* What a whole-site restore on this site puts back and what it does not
|
|
681
|
+
* (#1945), in the words admins read: the labels of the "not restored"
|
|
682
|
+
* notification (`restoreLabel`, `NEVER_IN_A_BACKUP`), so the restore card
|
|
683
|
+
* and the notification after the restart say the same. Read from the
|
|
684
|
+
* managers on this site, not from any backup: a backup can hold less (the
|
|
685
|
+
* credentials store of one taken without the backup key), never more.
|
|
686
|
+
*/
|
|
687
|
+
siteRestoreScope(): {
|
|
688
|
+
restored: string[];
|
|
689
|
+
notRestored: string[];
|
|
690
|
+
};
|
|
691
|
+
/**
|
|
692
|
+
* The restore waiting for the next start, whichever it is (#1945): a site
|
|
693
|
+
* restore, or a databases-only one. A site restore stages the databases
|
|
694
|
+
* too, so it is the one named while both records are there.
|
|
695
|
+
*/
|
|
696
|
+
getStagedRestore(): {
|
|
697
|
+
scope: RestoreScope;
|
|
698
|
+
source: string;
|
|
699
|
+
stagedBy: string;
|
|
700
|
+
} | null;
|
|
701
|
+
/**
|
|
702
|
+
* Throw away the restore waiting for the next start, whichever it is
|
|
703
|
+
* (#1945): a staged site restore through `cancelStagedSiteRestore`, a
|
|
704
|
+
* databases-only one through `DatabaseManager.cancelStagedRestore`. Each
|
|
705
|
+
* records `backup-restore-cancel` first (refuse).
|
|
706
|
+
*/
|
|
707
|
+
cancelStagedRestore(ctx: ActorContext): Promise<{
|
|
708
|
+
scope: RestoreScope;
|
|
709
|
+
source: string;
|
|
710
|
+
}>;
|
|
711
|
+
/**
|
|
712
|
+
* Start a restore of a stored backup from Admin → Backup: recorded, then a
|
|
713
|
+
* fresh backup of the live data and the staging run in the background, and
|
|
714
|
+
* admins are told when it is staged or why it was not. Nothing changes
|
|
715
|
+
* until the next restart, which the site never does by itself. Refused
|
|
716
|
+
* while another is being prepared, and when a restore is already staged
|
|
717
|
+
* unless `replace` says to replace it.
|
|
718
|
+
*
|
|
719
|
+
* - `scope: 'databases'` (the default; #1723, D1): the databases only.
|
|
720
|
+
* Refused while a site restore is staged, since that includes them.
|
|
721
|
+
* - `scope: 'site'` (#1945): a whole-site restore, every manager whose
|
|
722
|
+
* restore is staged and the instance .env, all or nothing
|
|
723
|
+
* (`stageSiteRestore`), from any stored backup. It replaces a staged
|
|
724
|
+
* restore of either kind only with `replace`.
|
|
725
|
+
*
|
|
726
|
+
* @returns the running restore, for a caller (a test) that waits on it.
|
|
727
|
+
*/
|
|
728
|
+
startRestore(ctx: ActorContext, filename: string, options?: {
|
|
729
|
+
replace?: boolean;
|
|
730
|
+
scope?: RestoreScope;
|
|
731
|
+
}): Promise<{
|
|
732
|
+
done: Promise<RestoreResults | null>;
|
|
733
|
+
}>;
|
|
734
|
+
/**
|
|
735
|
+
* List all available backups
|
|
736
|
+
* @returns {Promise<BackupFileInfo[]>} List of backup files with metadata
|
|
737
|
+
*/
|
|
738
|
+
listBackups(): Promise<BackupFileInfo[]>;
|
|
739
|
+
/**
|
|
740
|
+
* Clean up old backups, keeping only maxBackups most recent
|
|
741
|
+
* @private
|
|
742
|
+
*/
|
|
743
|
+
private cleanupOldBackups;
|
|
744
|
+
/**
|
|
745
|
+
* Get the most recent backup file path
|
|
746
|
+
* @returns {Promise<string | null>} Path to most recent backup, or null if none exist
|
|
747
|
+
*/
|
|
748
|
+
getLatestBackup(): Promise<string | null>;
|
|
749
|
+
/**
|
|
750
|
+
* Return current auto-backup configuration and last backup info for the admin UI.
|
|
751
|
+
*/
|
|
752
|
+
getAutoBackupStatus(): Promise<{
|
|
753
|
+
config: AutoBackupConfig;
|
|
754
|
+
lastBackup: Date | null;
|
|
755
|
+
}>;
|
|
756
|
+
/**
|
|
757
|
+
* Update auto-backup configuration and persist to config store.
|
|
758
|
+
* Restarts the scheduler with the new settings.
|
|
759
|
+
*/
|
|
760
|
+
updateAutoBackupConfig(config: Partial<AutoBackupConfig>, ctx: ActorContext): Promise<void>;
|
|
761
|
+
/**
|
|
762
|
+
* Automatic backups as a scheduled job (#1720, epic #1611).
|
|
763
|
+
*
|
|
764
|
+
* They ran from a timer that checked every minute for an exact match of the
|
|
765
|
+
* clock, so a server that was down at the backup time — or a tick that
|
|
766
|
+
* landed a minute late — skipped that backup and left no record that it had
|
|
767
|
+
* (operator: backups are never silent, #1563). As a scheduled job a backup
|
|
768
|
+
* missed while the server was down runs once when it is back (`catchUp:
|
|
769
|
+
* 'latest'`), a failed one is tried again and then reported, and the run
|
|
770
|
+
* survives a restart. A backup still running when the next one comes due is
|
|
771
|
+
* not doubled (`overlap: 'skip'`).
|
|
772
|
+
*
|
|
773
|
+
* Called at start and whenever the settings change. With automatic backups
|
|
774
|
+
* off, or a setting that cannot be read, the job is registered without a
|
|
775
|
+
* schedule: it can still be run by hand, and nothing runs by itself.
|
|
776
|
+
*/
|
|
777
|
+
private scheduleAutoBackup;
|
|
778
|
+
}
|
|
779
|
+
export default BackupManager;
|