@lumy-pack/syncpoint 0.0.3 → 0.0.5
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/assets/config.default.yml +15 -7
- package/assets/schemas/config.schema.json +68 -0
- package/assets/schemas/metadata.schema.json +121 -0
- package/assets/schemas/template.schema.json +61 -0
- package/assets/template.example.yml +6 -5
- package/dist/cli.mjs +746 -338
- package/dist/commands/Migrate.d.ts +2 -0
- package/dist/constants.d.ts +0 -1
- package/dist/core/migrate.d.ts +11 -0
- package/dist/index.cjs +225 -80
- package/dist/index.mjs +226 -81
- package/dist/utils/types.d.ts +11 -1
- package/dist/version.d.ts +1 -1
- package/package.json +5 -6
package/dist/constants.d.ts
CHANGED
|
@@ -5,7 +5,6 @@ export declare const METADATA_FILENAME = "_metadata.json";
|
|
|
5
5
|
export declare const LARGE_FILE_THRESHOLD: number;
|
|
6
6
|
export declare const MAX_RETRY = 3;
|
|
7
7
|
export declare const SENSITIVE_PATTERNS: string[];
|
|
8
|
-
export declare const REMOTE_SCRIPT_PATTERN: RegExp;
|
|
9
8
|
export declare const BACKUPS_DIR = "backups";
|
|
10
9
|
export declare const TEMPLATES_DIR = "templates";
|
|
11
10
|
export declare const SCRIPTS_DIR = "scripts";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MigrateResult } from '../utils/types.js';
|
|
2
|
+
export interface DiffResult {
|
|
3
|
+
added: string[][];
|
|
4
|
+
removed: string[][];
|
|
5
|
+
existing: string[][];
|
|
6
|
+
}
|
|
7
|
+
export declare function diffConfigFields(userData: unknown): DiffResult;
|
|
8
|
+
export declare function buildMigratedDocument(templateText: string, userData: unknown, diff: DiffResult): string;
|
|
9
|
+
export declare function migrateConfig(options?: {
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
}): Promise<MigrateResult>;
|
package/dist/index.cjs
CHANGED
|
@@ -126,6 +126,76 @@ function getSubDir(sub) {
|
|
|
126
126
|
return (0, import_node_path2.join)(getAppDir(), sub);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// assets/schemas/config.schema.json
|
|
130
|
+
var config_schema_default = {
|
|
131
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
132
|
+
title: "Syncpoint Config",
|
|
133
|
+
description: "Configuration for syncpoint backup tool",
|
|
134
|
+
type: "object",
|
|
135
|
+
required: [
|
|
136
|
+
"backup"
|
|
137
|
+
],
|
|
138
|
+
properties: {
|
|
139
|
+
backup: {
|
|
140
|
+
type: "object",
|
|
141
|
+
description: "Backup configuration",
|
|
142
|
+
required: [
|
|
143
|
+
"targets",
|
|
144
|
+
"exclude",
|
|
145
|
+
"filename"
|
|
146
|
+
],
|
|
147
|
+
properties: {
|
|
148
|
+
targets: {
|
|
149
|
+
type: "array",
|
|
150
|
+
description: "List of files/directories to backup. Supports literal paths (e.g. ~/.zshrc), glob patterns (e.g. ~/.config/*.conf), and regex patterns (e.g. /\\.conf$/).",
|
|
151
|
+
items: {
|
|
152
|
+
type: "string",
|
|
153
|
+
validPattern: true
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
exclude: {
|
|
157
|
+
type: "array",
|
|
158
|
+
description: "List of patterns to exclude from backup. Supports glob (e.g. **/*.swp) and regex (e.g. /\\.bak$/) patterns.",
|
|
159
|
+
items: {
|
|
160
|
+
type: "string",
|
|
161
|
+
validPattern: true
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
filename: {
|
|
165
|
+
type: "string",
|
|
166
|
+
description: "Backup archive filename pattern. Available variables: {hostname}, {datetime}.",
|
|
167
|
+
minLength: 1
|
|
168
|
+
},
|
|
169
|
+
destination: {
|
|
170
|
+
type: "string",
|
|
171
|
+
description: "Backup archive destination path. Default: ~/.syncpoint/backups/"
|
|
172
|
+
},
|
|
173
|
+
includeSensitiveFiles: {
|
|
174
|
+
type: "boolean",
|
|
175
|
+
description: "Include sensitive files (SSH keys, certificates, etc.) in backup. When false (default), files matching sensitive patterns (id_rsa, id_ed25519, *.pem, *.key) are automatically excluded."
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
additionalProperties: false
|
|
179
|
+
},
|
|
180
|
+
scripts: {
|
|
181
|
+
type: "object",
|
|
182
|
+
description: "Scripts configuration",
|
|
183
|
+
properties: {
|
|
184
|
+
includeInBackup: {
|
|
185
|
+
type: "boolean",
|
|
186
|
+
description: "Whether to include scripts/ directory in backup. Default: true."
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
additionalProperties: false
|
|
190
|
+
},
|
|
191
|
+
"yaml-language-server": {
|
|
192
|
+
type: "string",
|
|
193
|
+
description: "Editor directive for schema association; ignored at runtime."
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
additionalProperties: false
|
|
197
|
+
};
|
|
198
|
+
|
|
129
199
|
// src/schemas/ajv.ts
|
|
130
200
|
var import_ajv = __toESM(require("ajv"), 1);
|
|
131
201
|
var import_ajv_formats = __toESM(require("ajv-formats"), 1);
|
|
@@ -231,45 +301,7 @@ ajv.addKeyword({
|
|
|
231
301
|
});
|
|
232
302
|
|
|
233
303
|
// src/schemas/config.schema.ts
|
|
234
|
-
var
|
|
235
|
-
type: "object",
|
|
236
|
-
required: ["backup"],
|
|
237
|
-
properties: {
|
|
238
|
-
backup: {
|
|
239
|
-
type: "object",
|
|
240
|
-
required: ["targets", "exclude", "filename"],
|
|
241
|
-
properties: {
|
|
242
|
-
targets: {
|
|
243
|
-
type: "array",
|
|
244
|
-
items: { type: "string", validPattern: true }
|
|
245
|
-
},
|
|
246
|
-
exclude: {
|
|
247
|
-
type: "array",
|
|
248
|
-
items: { type: "string", validPattern: true }
|
|
249
|
-
},
|
|
250
|
-
filename: {
|
|
251
|
-
type: "string",
|
|
252
|
-
minLength: 1
|
|
253
|
-
},
|
|
254
|
-
destination: {
|
|
255
|
-
type: "string"
|
|
256
|
-
}
|
|
257
|
-
},
|
|
258
|
-
additionalProperties: false
|
|
259
|
-
},
|
|
260
|
-
scripts: {
|
|
261
|
-
type: "object",
|
|
262
|
-
properties: {
|
|
263
|
-
includeInBackup: {
|
|
264
|
-
type: "boolean"
|
|
265
|
-
}
|
|
266
|
-
},
|
|
267
|
-
additionalProperties: false
|
|
268
|
-
}
|
|
269
|
-
},
|
|
270
|
-
additionalProperties: false
|
|
271
|
-
};
|
|
272
|
-
var validate2 = ajv.compile(configSchema);
|
|
304
|
+
var validate2 = ajv.compile(config_schema_default);
|
|
273
305
|
function validateConfig(data) {
|
|
274
306
|
const valid = validate2(data);
|
|
275
307
|
if (valid) return { valid: true };
|
|
@@ -429,7 +461,7 @@ function generateFilename(pattern, options) {
|
|
|
429
461
|
var import_promises3 = require("fs/promises");
|
|
430
462
|
var import_node_path5 = require("path");
|
|
431
463
|
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
432
|
-
var ANSI_RE =
|
|
464
|
+
var ANSI_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
433
465
|
function stripAnsi(str) {
|
|
434
466
|
return str.replace(ANSI_RE, "");
|
|
435
467
|
}
|
|
@@ -485,8 +517,11 @@ var logger = {
|
|
|
485
517
|
var import_node_crypto = require("crypto");
|
|
486
518
|
var import_promises4 = require("fs/promises");
|
|
487
519
|
|
|
488
|
-
//
|
|
489
|
-
var
|
|
520
|
+
// assets/schemas/metadata.schema.json
|
|
521
|
+
var metadata_schema_default = {
|
|
522
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
523
|
+
title: "Syncpoint Backup Metadata",
|
|
524
|
+
description: "Metadata stored inside backup archives as _metadata.json",
|
|
490
525
|
type: "object",
|
|
491
526
|
required: [
|
|
492
527
|
"version",
|
|
@@ -499,57 +534,114 @@ var metadataSchema = {
|
|
|
499
534
|
"summary"
|
|
500
535
|
],
|
|
501
536
|
properties: {
|
|
502
|
-
version: {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
537
|
+
version: {
|
|
538
|
+
type: "string",
|
|
539
|
+
description: "Metadata schema version."
|
|
540
|
+
},
|
|
541
|
+
toolVersion: {
|
|
542
|
+
type: "string",
|
|
543
|
+
description: "Syncpoint tool version used to create the backup."
|
|
544
|
+
},
|
|
545
|
+
createdAt: {
|
|
546
|
+
type: "string",
|
|
547
|
+
description: "ISO 8601 timestamp of backup creation."
|
|
548
|
+
},
|
|
549
|
+
hostname: {
|
|
550
|
+
type: "string",
|
|
551
|
+
description: "Hostname of the machine where the backup was created."
|
|
552
|
+
},
|
|
506
553
|
system: {
|
|
507
554
|
type: "object",
|
|
555
|
+
description: "System information at backup time.",
|
|
508
556
|
required: ["platform", "release", "arch"],
|
|
509
557
|
properties: {
|
|
510
|
-
platform: {
|
|
511
|
-
|
|
512
|
-
|
|
558
|
+
platform: {
|
|
559
|
+
type: "string",
|
|
560
|
+
description: "Operating system platform (e.g. darwin, linux)."
|
|
561
|
+
},
|
|
562
|
+
release: {
|
|
563
|
+
type: "string",
|
|
564
|
+
description: "OS kernel release version."
|
|
565
|
+
},
|
|
566
|
+
arch: {
|
|
567
|
+
type: "string",
|
|
568
|
+
description: "CPU architecture (e.g. arm64, x64)."
|
|
569
|
+
}
|
|
513
570
|
},
|
|
514
571
|
additionalProperties: false
|
|
515
572
|
},
|
|
516
573
|
config: {
|
|
517
574
|
type: "object",
|
|
575
|
+
description: "Backup configuration snapshot.",
|
|
518
576
|
required: ["filename"],
|
|
519
577
|
properties: {
|
|
520
|
-
filename: {
|
|
521
|
-
|
|
578
|
+
filename: {
|
|
579
|
+
type: "string",
|
|
580
|
+
description: "Filename pattern used for the backup."
|
|
581
|
+
},
|
|
582
|
+
destination: {
|
|
583
|
+
type: "string",
|
|
584
|
+
description: "Custom destination path, if configured."
|
|
585
|
+
}
|
|
522
586
|
},
|
|
523
587
|
additionalProperties: false
|
|
524
588
|
},
|
|
525
589
|
files: {
|
|
526
590
|
type: "array",
|
|
591
|
+
description: "List of files included in the backup.",
|
|
527
592
|
items: {
|
|
528
593
|
type: "object",
|
|
529
594
|
required: ["path", "absolutePath", "size", "hash"],
|
|
530
595
|
properties: {
|
|
531
|
-
path: {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
596
|
+
path: {
|
|
597
|
+
type: "string",
|
|
598
|
+
description: "Display path (e.g. ~/.zshrc)."
|
|
599
|
+
},
|
|
600
|
+
absolutePath: {
|
|
601
|
+
type: "string",
|
|
602
|
+
description: "Full filesystem path."
|
|
603
|
+
},
|
|
604
|
+
size: {
|
|
605
|
+
type: "number",
|
|
606
|
+
description: "File size in bytes.",
|
|
607
|
+
minimum: 0
|
|
608
|
+
},
|
|
609
|
+
hash: {
|
|
610
|
+
type: "string",
|
|
611
|
+
description: "SHA-256 hash of file contents."
|
|
612
|
+
},
|
|
613
|
+
type: {
|
|
614
|
+
type: "string",
|
|
615
|
+
description: "File type (e.g. symlink, directory)."
|
|
616
|
+
}
|
|
536
617
|
},
|
|
537
618
|
additionalProperties: false
|
|
538
619
|
}
|
|
539
620
|
},
|
|
540
621
|
summary: {
|
|
541
622
|
type: "object",
|
|
623
|
+
description: "Backup summary statistics.",
|
|
542
624
|
required: ["fileCount", "totalSize"],
|
|
543
625
|
properties: {
|
|
544
|
-
fileCount: {
|
|
545
|
-
|
|
626
|
+
fileCount: {
|
|
627
|
+
type: "integer",
|
|
628
|
+
description: "Total number of files in the backup.",
|
|
629
|
+
minimum: 0
|
|
630
|
+
},
|
|
631
|
+
totalSize: {
|
|
632
|
+
type: "number",
|
|
633
|
+
description: "Total size of all files in bytes.",
|
|
634
|
+
minimum: 0
|
|
635
|
+
}
|
|
546
636
|
},
|
|
547
637
|
additionalProperties: false
|
|
548
638
|
}
|
|
549
639
|
},
|
|
550
640
|
additionalProperties: false
|
|
551
641
|
};
|
|
552
|
-
|
|
642
|
+
|
|
643
|
+
// src/schemas/metadata.schema.ts
|
|
644
|
+
var validate3 = ajv.compile(metadata_schema_default);
|
|
553
645
|
function validateMetadata(data) {
|
|
554
646
|
const valid = validate3(data);
|
|
555
647
|
if (valid) return { valid: true };
|
|
@@ -560,7 +652,7 @@ function validateMetadata(data) {
|
|
|
560
652
|
}
|
|
561
653
|
|
|
562
654
|
// src/version.ts
|
|
563
|
-
var VERSION = "0.0.
|
|
655
|
+
var VERSION = "0.0.5";
|
|
564
656
|
|
|
565
657
|
// src/core/metadata.ts
|
|
566
658
|
var METADATA_VERSION = "1.0.0";
|
|
@@ -611,7 +703,8 @@ async function collectFileInfo(absolutePath, logicalPath) {
|
|
|
611
703
|
}
|
|
612
704
|
let hash;
|
|
613
705
|
if (lstats.isSymbolicLink()) {
|
|
614
|
-
|
|
706
|
+
const linkTarget = await (0, import_promises4.readlink)(absolutePath);
|
|
707
|
+
hash = `sha256:${(0, import_node_crypto.createHash)("sha256").update(linkTarget).digest("hex")}`;
|
|
615
708
|
} else {
|
|
616
709
|
hash = await computeFileHash(absolutePath);
|
|
617
710
|
}
|
|
@@ -672,7 +765,8 @@ async function extractArchive(archivePath, destDir) {
|
|
|
672
765
|
const normalizedPath = (0, import_node_path6.normalize)(path);
|
|
673
766
|
if (normalizedPath.includes("..")) return false;
|
|
674
767
|
if (normalizedPath.startsWith("/")) return false;
|
|
675
|
-
if (entry.type === "SymbolicLink" || entry.type === "Link")
|
|
768
|
+
if ("type" in entry && (entry.type === "SymbolicLink" || entry.type === "Link"))
|
|
769
|
+
return false;
|
|
676
770
|
return true;
|
|
677
771
|
}
|
|
678
772
|
});
|
|
@@ -743,6 +837,10 @@ async function scanTargets(config) {
|
|
|
743
837
|
});
|
|
744
838
|
for (const match of allFiles) {
|
|
745
839
|
if (regex.test(match) && !isExcluded(match)) {
|
|
840
|
+
if (!config.backup.includeSensitiveFiles && isSensitiveFile(match)) {
|
|
841
|
+
logger.warn(`Sensitive file excluded: ${match}`);
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
746
844
|
const entry = await collectFileInfo(match, match);
|
|
747
845
|
found.push(entry);
|
|
748
846
|
}
|
|
@@ -771,6 +869,10 @@ async function scanTargets(config) {
|
|
|
771
869
|
});
|
|
772
870
|
for (const match of matches) {
|
|
773
871
|
if (!isExcluded(match)) {
|
|
872
|
+
if (!config.backup.includeSensitiveFiles && isSensitiveFile(match)) {
|
|
873
|
+
logger.warn(`Sensitive file excluded: ${match}`);
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
774
876
|
const entry = await collectFileInfo(match, match);
|
|
775
877
|
found.push(entry);
|
|
776
878
|
}
|
|
@@ -804,6 +906,10 @@ async function scanTargets(config) {
|
|
|
804
906
|
});
|
|
805
907
|
for (const match of matches) {
|
|
806
908
|
if (!isExcluded(match)) {
|
|
909
|
+
if (!config.backup.includeSensitiveFiles && isSensitiveFile(match)) {
|
|
910
|
+
logger.warn(`Sensitive file excluded: ${match}`);
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
807
913
|
const entry = await collectFileInfo(match, match);
|
|
808
914
|
found.push(entry);
|
|
809
915
|
}
|
|
@@ -815,15 +921,16 @@ async function scanTargets(config) {
|
|
|
815
921
|
if (isExcluded(absPath)) {
|
|
816
922
|
continue;
|
|
817
923
|
}
|
|
924
|
+
if (!config.backup.includeSensitiveFiles && isSensitiveFile(absPath)) {
|
|
925
|
+
logger.warn(`Sensitive file excluded: ${target}`);
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
818
928
|
const entry = await collectFileInfo(absPath, absPath);
|
|
819
929
|
if (entry.size > LARGE_FILE_THRESHOLD) {
|
|
820
930
|
logger.warn(
|
|
821
931
|
`Large file (>${Math.round(LARGE_FILE_THRESHOLD / 1024 / 1024)}MB): ${target}`
|
|
822
932
|
);
|
|
823
933
|
}
|
|
824
|
-
if (isSensitiveFile(absPath)) {
|
|
825
|
-
logger.warn(`Sensitive file detected: ${target}`);
|
|
826
|
-
}
|
|
827
934
|
found.push(entry);
|
|
828
935
|
}
|
|
829
936
|
}
|
|
@@ -857,7 +964,7 @@ async function createBackup(config, options = {}) {
|
|
|
857
964
|
}
|
|
858
965
|
}
|
|
859
966
|
let allFiles = [...found];
|
|
860
|
-
if (config.scripts
|
|
967
|
+
if (config.scripts?.includeInBackup) {
|
|
861
968
|
const scripts = await collectScripts();
|
|
862
969
|
allFiles = [...allFiles, ...scripts];
|
|
863
970
|
}
|
|
@@ -1055,27 +1162,61 @@ var import_promises8 = require("fs/promises");
|
|
|
1055
1162
|
var import_node_path9 = require("path");
|
|
1056
1163
|
var import_yaml2 = __toESM(require("yaml"), 1);
|
|
1057
1164
|
|
|
1058
|
-
//
|
|
1059
|
-
var
|
|
1165
|
+
// assets/schemas/template.schema.json
|
|
1166
|
+
var template_schema_default = {
|
|
1167
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
1168
|
+
title: "Syncpoint Template",
|
|
1169
|
+
description: "Provisioning template for syncpoint",
|
|
1060
1170
|
type: "object",
|
|
1061
1171
|
required: ["name", "steps"],
|
|
1062
1172
|
properties: {
|
|
1063
|
-
name: {
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1173
|
+
name: {
|
|
1174
|
+
type: "string",
|
|
1175
|
+
description: "Template name.",
|
|
1176
|
+
minLength: 1
|
|
1177
|
+
},
|
|
1178
|
+
description: {
|
|
1179
|
+
type: "string",
|
|
1180
|
+
description: "Template description."
|
|
1181
|
+
},
|
|
1182
|
+
backup: {
|
|
1183
|
+
type: "string",
|
|
1184
|
+
description: "Backup name to restore automatically after provisioning."
|
|
1185
|
+
},
|
|
1186
|
+
sudo: {
|
|
1187
|
+
type: "boolean",
|
|
1188
|
+
description: "Whether sudo privilege is required. If true, requests sudo authentication before execution."
|
|
1189
|
+
},
|
|
1067
1190
|
steps: {
|
|
1068
1191
|
type: "array",
|
|
1192
|
+
description: "List of provisioning steps. At least 1 step required.",
|
|
1069
1193
|
minItems: 1,
|
|
1070
1194
|
items: {
|
|
1071
1195
|
type: "object",
|
|
1072
1196
|
required: ["name", "command"],
|
|
1073
1197
|
properties: {
|
|
1074
|
-
name: {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1198
|
+
name: {
|
|
1199
|
+
type: "string",
|
|
1200
|
+
description: "Step name.",
|
|
1201
|
+
minLength: 1
|
|
1202
|
+
},
|
|
1203
|
+
description: {
|
|
1204
|
+
type: "string",
|
|
1205
|
+
description: "Step description."
|
|
1206
|
+
},
|
|
1207
|
+
command: {
|
|
1208
|
+
type: "string",
|
|
1209
|
+
description: "Shell command to execute.",
|
|
1210
|
+
minLength: 1
|
|
1211
|
+
},
|
|
1212
|
+
skip_if: {
|
|
1213
|
+
type: "string",
|
|
1214
|
+
description: "Skip this step if this command exits with code 0."
|
|
1215
|
+
},
|
|
1216
|
+
continue_on_error: {
|
|
1217
|
+
type: "boolean",
|
|
1218
|
+
description: "Continue to next step even if this fails. Default: false."
|
|
1219
|
+
}
|
|
1079
1220
|
},
|
|
1080
1221
|
additionalProperties: false
|
|
1081
1222
|
}
|
|
@@ -1083,7 +1224,9 @@ var templateSchema = {
|
|
|
1083
1224
|
},
|
|
1084
1225
|
additionalProperties: false
|
|
1085
1226
|
};
|
|
1086
|
-
|
|
1227
|
+
|
|
1228
|
+
// src/schemas/template.schema.ts
|
|
1229
|
+
var validate4 = ajv.compile(template_schema_default);
|
|
1087
1230
|
function validateTemplate(data) {
|
|
1088
1231
|
const valid = validate4(data);
|
|
1089
1232
|
if (valid) return { valid: true };
|
|
@@ -1209,8 +1352,10 @@ async function executeStep(step) {
|
|
|
1209
1352
|
output: output || void 0
|
|
1210
1353
|
};
|
|
1211
1354
|
} catch (err) {
|
|
1212
|
-
const error = err;
|
|
1213
|
-
const
|
|
1355
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1356
|
+
const stdout = err?.stdout ?? "";
|
|
1357
|
+
const stderr = err?.stderr ?? "";
|
|
1358
|
+
const errorOutput = [stdout, stderr, error.message].filter(Boolean).join("\n").trim();
|
|
1214
1359
|
return {
|
|
1215
1360
|
name: step.name,
|
|
1216
1361
|
status: "failed",
|