@covro/covro 0.2.1-dev.6 → 0.2.1-dev.7
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 +7 -0
- package/README.zh-CN.md +7 -0
- package/dist/backups/factory.js +22 -0
- package/dist/backups/factory.js.map +7 -0
- package/dist/backups/lock.js +22 -0
- package/dist/backups/lock.js.map +7 -0
- package/dist/backups/manager.js +170 -0
- package/dist/backups/manager.js.map +7 -0
- package/dist/backups/restore.js +188 -0
- package/dist/backups/restore.js.map +7 -0
- package/dist/backups/store.js +274 -0
- package/dist/backups/store.js.map +7 -0
- package/dist/cli/backup.js +84 -0
- package/dist/cli/backup.js.map +7 -0
- package/dist/cli/daemon.js +7 -0
- package/dist/cli/daemon.js.map +2 -2
- package/dist/cli/main.js +35 -5
- package/dist/cli/main.js.map +2 -2
- package/dist/cli/paths.js.map +2 -2
- package/dist/server/db/history.js +60 -0
- package/dist/server/db/history.js.map +7 -0
- package/dist/server/db/index.js +11 -1
- package/dist/server/db/index.js.map +2 -2
- package/dist/server/db/migrate.js +8 -18
- package/dist/server/db/migrate.js.map +2 -2
- package/dist/server/http.js +21 -3
- package/dist/server/http.js.map +2 -2
- package/dist/server/main.js +14 -3
- package/dist/server/main.js.map +2 -2
- package/dist/server/service.js +9 -5
- package/dist/server/service.js.map +2 -2
- package/dist/shared/backups.js +55 -0
- package/dist/shared/backups.js.map +7 -0
- package/dist/shared/database.js +15 -0
- package/dist/shared/database.js.map +7 -0
- package/dist/update-worker.cjs +934 -388
- package/dist/updates/files.js +2 -1
- package/dist/updates/files.js.map +2 -2
- package/dist/updates/transaction.js +3 -24
- package/dist/updates/transaction.js.map +2 -2
- package/dist/updates/worker.js +69 -19
- package/dist/updates/worker.js.map +2 -2
- package/dist/web/assets/{abnfDiagram-WEYOIA3C-BkC1EVBk.js → abnfDiagram-WEYOIA3C-Baw8Q2RL.js} +1 -1
- package/dist/web/assets/{arc-CYGT9huc.js → arc-BvZHXzPr.js} +1 -1
- package/dist/web/assets/{architecture-WOLXFQ4H-DCsF3gVg.js → architecture-WOLXFQ4H-DrQ2OxuY.js} +1 -1
- package/dist/web/assets/{architectureDiagram-ZHVYKT53-Cn6kovuz.js → architectureDiagram-ZHVYKT53-D96hX75H.js} +1 -1
- package/dist/web/assets/{blockDiagram-RJAE7QA3-CNk2zXm9.js → blockDiagram-RJAE7QA3-Csce-aWb.js} +1 -1
- package/dist/web/assets/{c4Diagram-XDPPPE54-CTuI_1XJ.js → c4Diagram-XDPPPE54-Dfu8ZQ6Y.js} +1 -1
- package/dist/web/assets/channel-DQhtAfM4.js +1 -0
- package/dist/web/assets/{chunk-2E7WHVMD-TrYs0qV3.js → chunk-2E7WHVMD-Vb63e-lu.js} +1 -1
- package/dist/web/assets/{chunk-4EMNKR2T-QL4-PZ4r.js → chunk-4EMNKR2T-DrY1P7q3.js} +1 -1
- package/dist/web/assets/{chunk-55XCU76K-yynn-EEr.js → chunk-55XCU76K-D70bjQ9E.js} +1 -1
- package/dist/web/assets/{chunk-7S6XSTFO-dzrEn-_W.js → chunk-7S6XSTFO-CfW90MUC.js} +1 -1
- package/dist/web/assets/{chunk-AQDTSKZB-DaNZMEGO.js → chunk-AQDTSKZB-u4KDDGBt.js} +1 -1
- package/dist/web/assets/{chunk-DSNLSHJZ-BnMtx8Ho.js → chunk-DSNLSHJZ-Cax3giJD.js} +1 -1
- package/dist/web/assets/{chunk-HAGFOTED-fg-JyiA5.js → chunk-HAGFOTED-BG9UMwzK.js} +1 -1
- package/dist/web/assets/{chunk-HNE4Q36P-CCsfPbcn.js → chunk-HNE4Q36P-BNxIfOgZ.js} +1 -1
- package/dist/web/assets/{chunk-NQOKRVIF-DLlOpcWZ.js → chunk-NQOKRVIF-DJ60e0Al.js} +1 -1
- package/dist/web/assets/{chunk-UHW7PFBP-B-sPKpeS.js → chunk-UHW7PFBP-C9gHYcjJ.js} +1 -1
- package/dist/web/assets/{chunk-VQ5W62QI-BVqGMzey.js → chunk-VQ5W62QI-TyHPGVfQ.js} +1 -1
- package/dist/web/assets/{chunk-WQQL4CDK-DcwJXNuh.js → chunk-WQQL4CDK-DeAauheh.js} +1 -1
- package/dist/web/assets/{chunk-XXDRQBXY-DRwLkqyU.js → chunk-XXDRQBXY-V-L20Tbi.js} +1 -1
- package/dist/web/assets/{chunk-YZO67HFG-BrrgBnrQ.js → chunk-YZO67HFG-LV5QVBiU.js} +1 -1
- package/dist/web/assets/{chunk-ZAUX2LTX-C7qvwkRr.js → chunk-ZAUX2LTX-CBsBBk7L.js} +1 -1
- package/dist/web/assets/{chunk-ZCHMQSOR-DdBd3Cvs.js → chunk-ZCHMQSOR-a2_9JHHn.js} +1 -1
- package/dist/web/assets/{classDiagram-v2-4MYWR3UA-Dn2RZb3v.js → classDiagram-v2-4MYWR3UA-77eiBCO3.js} +1 -1
- package/dist/web/assets/{cose-bilkent-JH36ORCC-BlXvtfgE.js → cose-bilkent-JH36ORCC-DDP73pnY.js} +1 -1
- package/dist/web/assets/{cynefin-EF2NZ3EQ-odDgTxc-.js → cynefin-EF2NZ3EQ-onpnpyxc.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-WF24UDRP-BySEoCW8.js → cynefinDiagram-WF24UDRP-BmLplbnH.js} +1 -1
- package/dist/web/assets/{dagre-NK4GIZVX-ihxmhB4X.js → dagre-NK4GIZVX-BhaMf_Gx.js} +1 -1
- package/dist/web/assets/{diagram-CBK3MWTC-Ckr5P3IM.js → diagram-CBK3MWTC-BaalTqFt.js} +1 -1
- package/dist/web/assets/{diagram-I5XIIV2D-D5BZIval.js → diagram-I5XIIV2D-4Tdshcyt.js} +1 -1
- package/dist/web/assets/{diagram-ODIL2UCW-BC5jjqqY.js → diagram-ODIL2UCW-SlEcZgod.js} +1 -1
- package/dist/web/assets/{diagram-PFBEJUXE-D_IdYKqC.js → diagram-PFBEJUXE-CosCAXbh.js} +1 -1
- package/dist/web/assets/{diagram-RJA3I4VV-DlPNsPqP.js → diagram-RJA3I4VV-HQElVL4B.js} +1 -1
- package/dist/web/assets/{diagram-TYUNFSNR-Ca2ANhGM.js → diagram-TYUNFSNR-BT2WDc21.js} +1 -1
- package/dist/web/assets/{dist-BSWkYZRr.js → dist-CrpGQKqw.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-V4P3TTPT-BrqvmeiB.js → ebnfDiagram-V4P3TTPT-BhK6AM0_.js} +1 -1
- package/dist/web/assets/{elk-QONTBCTQ-BfXeOE2E.js → elk-QONTBCTQ-CS7DfCnD.js} +1 -1
- package/dist/web/assets/{erDiagram-KYQIGQAA-CMuYoCVJ.js → erDiagram-KYQIGQAA-DxVnuqFK.js} +1 -1
- package/dist/web/assets/{eventmodeling-K75KTNOO-9s4vQqv1.js → eventmodeling-K75KTNOO-oC_YeomD.js} +1 -1
- package/dist/web/assets/flowDiagram-IUGLLWBU-Bspg0rQz.js +1 -0
- package/dist/web/assets/{ganttDiagram-R7GPNTJY-B-Xmfmm2.js → ganttDiagram-R7GPNTJY-CquUpiE1.js} +1 -1
- package/dist/web/assets/{gitGraph-VSP46ZUC-ZSbSQ7jf.js → gitGraph-VSP46ZUC-BTtGQYsh.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-PDYL7XHY-BhxijLaZ.js → gitGraphDiagram-PDYL7XHY-BsokGXYo.js} +1 -1
- package/dist/web/assets/index-B8tsymVC.js +147 -0
- package/dist/web/assets/{index-mkDvqMQ3.css → index-COSho7m2.css} +1 -1
- package/dist/web/assets/{info-OHQRW6UA-CTSCkjt3.js → info-OHQRW6UA-CvGN4gWh.js} +1 -1
- package/dist/web/assets/{infoDiagram-MVDRKAX5-D46MTesD.js → infoDiagram-MVDRKAX5-BF9Rp3BT.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-HPEHV2GE-Bafu9NOk.js → ishikawaDiagram-HPEHV2GE-DtD20ngU.js} +1 -1
- package/dist/web/assets/{journeyDiagram-BOSHW7O7-CRsIi1Ry.js → journeyDiagram-BOSHW7O7-gB16trHV.js} +1 -1
- package/dist/web/assets/{kanban-definition-FNSA4HC5-DjIT_uLs.js → kanban-definition-FNSA4HC5-D0aKOsE2.js} +1 -1
- package/dist/web/assets/{line-B5MDdgVs.js → line-BR5dqPmF.js} +1 -1
- package/dist/web/assets/{linear-wm7JWxlT.js → linear-OsiQaPpK.js} +1 -1
- package/dist/web/assets/{mermaid-parser.core-C7jTW4sZ.js → mermaid-parser.core-MEuwYVot.js} +3 -3
- package/dist/web/assets/{mermaid.core-DcWRGjP5.js → mermaid.core-CVKnnz65.js} +4 -4
- package/dist/web/assets/{mindmap-definition-6OY57UG3-qY6vV0UI.js → mindmap-definition-6OY57UG3-5aHA-kPJ.js} +1 -1
- package/dist/web/assets/{packet-JDAUHWVQ-9B7vLYK0.js → packet-JDAUHWVQ-uj48AF5r.js} +1 -1
- package/dist/web/assets/{pegDiagram-ECIUJNBX-B7gLZ_Fs.js → pegDiagram-ECIUJNBX-BkVIHLBX.js} +1 -1
- package/dist/web/assets/{pie-XZMESJXO-MfazZSiE.js → pie-XZMESJXO-D3hy3nD7.js} +1 -1
- package/dist/web/assets/{pieDiagram-GNMPY74T-1ahagzzC.js → pieDiagram-GNMPY74T-CBAqGMEo.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-D57K4MC3-C-GkwjOp.js → quadrantDiagram-D57K4MC3-C35kvNRB.js} +1 -1
- package/dist/web/assets/{radar-ABXABTNO-CgutvWTi.js → radar-ABXABTNO-3LWE6J2Y.js} +1 -1
- package/dist/web/assets/{railroad-I3PHUGI6-Bl9LJINm.js → railroad-I3PHUGI6-B6Mc9Djl.js} +1 -1
- package/dist/web/assets/{railroad-abnf-LNEFI6M7-CGRpAnuU.js → railroad-abnf-LNEFI6M7-DJnbwbpn.js} +1 -1
- package/dist/web/assets/{railroad-ebnf-DWYD2UWJ-D6A47xZS.js → railroad-ebnf-DWYD2UWJ-BFXAvY4k.js} +1 -1
- package/dist/web/assets/{railroad-peg-JR7BVNR7-DxGphrFO.js → railroad-peg-JR7BVNR7-JYGb2Oq5.js} +1 -1
- package/dist/web/assets/{railroadDiagram-Y72NRBL7-D8P17OV4.js → railroadDiagram-Y72NRBL7-uBIggR0w.js} +1 -1
- package/dist/web/assets/{requirementDiagram-EQYSW5RA-c93_xAaQ.js → requirementDiagram-EQYSW5RA-CDWglrxJ.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-SY24JHO5-FankqwcZ.js → sankeyDiagram-SY24JHO5-BT16hnTl.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-BLEHNHQU-CWH5SeL1.js → sequenceDiagram-BLEHNHQU-eFJ27rVk.js} +1 -1
- package/dist/web/assets/{src-BXQOmVZv.js → src-BlOCBHYS.js} +1 -1
- package/dist/web/assets/{stateDiagram-v2-A6IESKWZ-DLfcw0e7.js → stateDiagram-v2-A6IESKWZ-CK_s-2Qv.js} +1 -1
- package/dist/web/assets/{swimlanes-B3ENRKFQ-DZ-yBiap.js → swimlanes-B3ENRKFQ-WLMrk9Lm.js} +1 -1
- package/dist/web/assets/swimlanesDiagram-AYQRV4AK-DmjebfnI.js +8 -0
- package/dist/web/assets/{timeline-definition-JXYKGLGB-B7XvxDHY.js → timeline-definition-JXYKGLGB-D0-xZS_V.js} +1 -1
- package/dist/web/assets/{treeView-D4JQ5SDB-DrB6wXJ0.js → treeView-D4JQ5SDB-TTivyB8T.js} +1 -1
- package/dist/web/assets/{treemap-SAJKECNS-BynFjsvx.js → treemap-SAJKECNS-DtU16wOO.js} +1 -1
- package/dist/web/assets/{usecaseDiagram-WPOLBNNX-BISzHOHN.js → usecaseDiagram-WPOLBNNX-DLFybW0F.js} +1 -1
- package/dist/web/assets/{vennDiagram-Q3Y3IDCW-BZwjTIST.js → vennDiagram-Q3Y3IDCW-DRDuYw70.js} +1 -1
- package/dist/web/assets/{wardley-7MLQ67FV-2bM0pMhO.js → wardley-7MLQ67FV-tZ-5qrqv.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-IVAIDKWA-CflkU8us.js → wardleyDiagram-IVAIDKWA-wRKeGCMl.js} +1 -1
- package/dist/web/assets/{xychartDiagram-4URNHDU4-DRspOlD3.js → xychartDiagram-4URNHDU4-B5-k-DHF.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +8 -3
- package/dist/web/assets/channel-BDxsxdm-.js +0 -1
- package/dist/web/assets/flowDiagram-IUGLLWBU-BHwP7Ha7.js +0 -1
- package/dist/web/assets/index-BrvtlTwN.js +0 -147
- package/dist/web/assets/swimlanesDiagram-AYQRV4AK-BlmdUBbW.js +0 -8
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { z, ZodError } from "zod";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { createReadStream } from "node:fs";
|
|
4
|
+
import { chmod, lstat, mkdir, open, readFile, readdir, rm } from "node:fs/promises";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
import lockfile from "proper-lockfile";
|
|
7
|
+
import {
|
|
8
|
+
backupIdSchema,
|
|
9
|
+
backupManifestSchema,
|
|
10
|
+
backupRecordSchema
|
|
11
|
+
} from "../shared/backups.js";
|
|
12
|
+
import { AppError } from "../shared/errors.js";
|
|
13
|
+
import {
|
|
14
|
+
inspectDatabase,
|
|
15
|
+
readMigrations,
|
|
16
|
+
requireRestorable
|
|
17
|
+
} from "../server/db/history.js";
|
|
18
|
+
import { MAX_CONFIG_BYTES } from "../server/config-store.js";
|
|
19
|
+
import { parseConfig, parseFileConfig } from "../server/config.js";
|
|
20
|
+
import { copyDurable, readJson, saveJson } from "../updates/files.js";
|
|
21
|
+
const backupDirectory = (home, dataDir) => join(home, "backups", createHash("sha256").update(resolve(dataDir)).digest("hex").slice(0, 16));
|
|
22
|
+
async function fingerprint(path) {
|
|
23
|
+
const info = await lstat(path);
|
|
24
|
+
if (!info.isFile() || info.isSymbolicLink())
|
|
25
|
+
throw new Error("Backup contains a non-regular file");
|
|
26
|
+
const hash = createHash("sha256");
|
|
27
|
+
for await (const chunk of createReadStream(path)) hash.update(chunk);
|
|
28
|
+
return { size: info.size, sha256: hash.digest("hex") };
|
|
29
|
+
}
|
|
30
|
+
function backupFailure(error) {
|
|
31
|
+
if (error instanceof SyntaxError || error instanceof ZodError)
|
|
32
|
+
return "\u670D\u52A1\u914D\u7F6E\u65E0\u6548\uFF0C\u5907\u4EFD\u672A\u5B8C\u6210\uFF1B\u8BF7\u68C0\u67E5\u914D\u7F6E\u540E\u91CD\u8BD5\u3002";
|
|
33
|
+
const code = error?.code;
|
|
34
|
+
if (code === "ENOSPC" || code === "SQLITE_FULL") return "\u78C1\u76D8\u7A7A\u95F4\u4E0D\u8DB3\uFF0C\u5907\u4EFD\u672A\u5B8C\u6210\u3002";
|
|
35
|
+
if (code === "EACCES" || code === "EPERM" || code === "EROFS")
|
|
36
|
+
return "\u65E0\u6CD5\u5199\u5165\u5907\u4EFD\u76EE\u5F55\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u6743\u9650\u3002";
|
|
37
|
+
if (code === "ELOCKED") return "\u914D\u7F6E\u6216\u5907\u4EFD\u76EE\u5F55\u6B63\u5728\u88AB\u5176\u4ED6\u64CD\u4F5C\u4F7F\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
|
|
38
|
+
return "\u5907\u4EFD\u9A8C\u8BC1\u6216\u6587\u4EF6\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u78C1\u76D8\u3001\u6743\u9650\u53CA\u6587\u4EF6\u5B8C\u6574\u6027\u540E\u91CD\u8BD5\u3002";
|
|
39
|
+
}
|
|
40
|
+
class BackupStore {
|
|
41
|
+
constructor(options) {
|
|
42
|
+
this.options = options;
|
|
43
|
+
}
|
|
44
|
+
options;
|
|
45
|
+
path(id) {
|
|
46
|
+
return join(this.options.directory, backupIdSchema.parse(id));
|
|
47
|
+
}
|
|
48
|
+
async record(record) {
|
|
49
|
+
await saveJson(join(this.path(record.id), "record.json"), record);
|
|
50
|
+
}
|
|
51
|
+
async list() {
|
|
52
|
+
let names;
|
|
53
|
+
try {
|
|
54
|
+
names = await readdir(this.options.directory);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error.code === "ENOENT") return [];
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
const records = [];
|
|
60
|
+
for (const id of names.filter((name) => backupIdSchema.safeParse(name).success)) {
|
|
61
|
+
try {
|
|
62
|
+
const value = await readJson(join(this.path(id), "record.json"));
|
|
63
|
+
if (value === void 0) continue;
|
|
64
|
+
const record = backupRecordSchema.parse(value);
|
|
65
|
+
if (record.id !== id) throw new Error("Backup record identity mismatch");
|
|
66
|
+
const parsed = backupManifestSchema.safeParse(
|
|
67
|
+
await readJson(join(this.path(id), "snapshot.json"))
|
|
68
|
+
);
|
|
69
|
+
if (parsed.success && parsed.data.id === id)
|
|
70
|
+
records.push({
|
|
71
|
+
...record,
|
|
72
|
+
path: this.path(id),
|
|
73
|
+
phase: ["waiting", "copying", "verifying"].includes(record.phase) ? "succeeded" : record.phase,
|
|
74
|
+
manifest: parsed.data
|
|
75
|
+
});
|
|
76
|
+
else
|
|
77
|
+
records.push({
|
|
78
|
+
...record,
|
|
79
|
+
path: this.path(id),
|
|
80
|
+
phase: record.phase === "succeeded" ? "corrupt" : record.phase,
|
|
81
|
+
manifest: void 0
|
|
82
|
+
});
|
|
83
|
+
} catch {
|
|
84
|
+
records.push({
|
|
85
|
+
id,
|
|
86
|
+
path: this.path(id),
|
|
87
|
+
phase: "corrupt",
|
|
88
|
+
trigger: "manual",
|
|
89
|
+
createdAt: 0,
|
|
90
|
+
updatedAt: 0,
|
|
91
|
+
message: "\u5907\u4EFD\u8BB0\u5F55\u635F\u574F\uFF0C\u4E0D\u80FD\u6062\u590D\u3002"
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return records.sort((a, b) => b.createdAt - a.createdAt);
|
|
96
|
+
}
|
|
97
|
+
async create(input) {
|
|
98
|
+
const { Sqlite, migrations, config, dataDir, appVersion, commit } = this.options;
|
|
99
|
+
const id = backupIdSchema.parse(input.id ?? randomUUID());
|
|
100
|
+
const directory = this.path(id);
|
|
101
|
+
await mkdir(this.options.directory, { recursive: true, mode: 448 });
|
|
102
|
+
if (!(await lstat(this.options.directory)).isDirectory() || (await lstat(this.options.directory)).isSymbolicLink())
|
|
103
|
+
throw new Error("Invalid backup directory");
|
|
104
|
+
await chmod(this.options.directory, 448);
|
|
105
|
+
if (input.reserved) {
|
|
106
|
+
const reserved = await readJson(join(directory, "record.json"));
|
|
107
|
+
if (reserved?.id !== id || reserved.phase !== "waiting" || await readJson(join(directory, "snapshot.json")))
|
|
108
|
+
throw new Error("Invalid backup reservation");
|
|
109
|
+
} else await mkdir(directory, { mode: 448 });
|
|
110
|
+
const record = {
|
|
111
|
+
id,
|
|
112
|
+
phase: "copying",
|
|
113
|
+
trigger: input.trigger,
|
|
114
|
+
createdAt: Date.now(),
|
|
115
|
+
updatedAt: Date.now(),
|
|
116
|
+
path: directory,
|
|
117
|
+
message: "\u6B63\u5728\u751F\u6210\u6570\u636E\u5E93\u4E0E\u914D\u7F6E\u5FEB\u7167"
|
|
118
|
+
};
|
|
119
|
+
let release;
|
|
120
|
+
let sqlite;
|
|
121
|
+
try {
|
|
122
|
+
await this.record(record);
|
|
123
|
+
input.progress?.({ ...record });
|
|
124
|
+
input.signal?.throwIfAborted();
|
|
125
|
+
release = await lockfile.lock(config, { realpath: false, retries: 0 });
|
|
126
|
+
const configStat = await lstat(config);
|
|
127
|
+
if (!configStat.isFile() || configStat.size > MAX_CONFIG_BYTES)
|
|
128
|
+
throw new Error("Invalid config file");
|
|
129
|
+
const contents = await readFile(config, "utf8");
|
|
130
|
+
if (parseConfig(JSON.parse(contents), config).dataDir !== resolve(dataDir))
|
|
131
|
+
throw new Error("Saved configuration no longer identifies the running data directory");
|
|
132
|
+
const configFile = await open(join(directory, "config.json"), "wx", 384);
|
|
133
|
+
try {
|
|
134
|
+
await configFile.writeFile(contents);
|
|
135
|
+
await configFile.sync();
|
|
136
|
+
} finally {
|
|
137
|
+
await configFile.close();
|
|
138
|
+
}
|
|
139
|
+
sqlite = new Sqlite(join(dataDir, "app.sqlite"), { readonly: true, fileMustExist: true });
|
|
140
|
+
const history = inspectDatabase(sqlite, migrations);
|
|
141
|
+
requireRestorable(history.schemaVersion, this.options.contract);
|
|
142
|
+
await sqlite.backup(join(directory, "app.sqlite"), {
|
|
143
|
+
progress: ({ totalPages, remainingPages }) => {
|
|
144
|
+
input.signal?.throwIfAborted();
|
|
145
|
+
input.progress?.({
|
|
146
|
+
...record,
|
|
147
|
+
progress: Math.round((totalPages - remainingPages) / totalPages * 100)
|
|
148
|
+
});
|
|
149
|
+
return 100;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
sqlite.close();
|
|
153
|
+
sqlite = void 0;
|
|
154
|
+
await chmod(join(directory, "app.sqlite"), 384);
|
|
155
|
+
if (await readFile(config, "utf8") !== contents)
|
|
156
|
+
throw new Error("Configuration changed externally during backup");
|
|
157
|
+
input.signal?.throwIfAborted();
|
|
158
|
+
record.phase = "verifying";
|
|
159
|
+
record.message = "\u6B63\u5728\u9A8C\u8BC1\u5907\u4EFD\u5B8C\u6574\u6027";
|
|
160
|
+
record.updatedAt = Date.now();
|
|
161
|
+
await this.record(record);
|
|
162
|
+
input.progress?.({ ...record });
|
|
163
|
+
const files = await Promise.all(
|
|
164
|
+
["app.sqlite", "config.json"].map(async (name) => ({
|
|
165
|
+
name,
|
|
166
|
+
...await fingerprint(join(directory, name))
|
|
167
|
+
}))
|
|
168
|
+
);
|
|
169
|
+
const manifest = backupManifestSchema.parse({
|
|
170
|
+
formatVersion: 1,
|
|
171
|
+
complete: true,
|
|
172
|
+
id,
|
|
173
|
+
createdAt: record.createdAt,
|
|
174
|
+
appVersion,
|
|
175
|
+
commit,
|
|
176
|
+
trigger: input.trigger,
|
|
177
|
+
configPath: config,
|
|
178
|
+
dataDir,
|
|
179
|
+
schemaVersion: history.schemaVersion,
|
|
180
|
+
migrationHashes: history.migrationHashes,
|
|
181
|
+
files,
|
|
182
|
+
protectedBy: input.protectedBy
|
|
183
|
+
});
|
|
184
|
+
this.checkDatabase(directory, manifest);
|
|
185
|
+
const file = await open(join(directory, "app.sqlite"), "r");
|
|
186
|
+
try {
|
|
187
|
+
await file.sync();
|
|
188
|
+
} finally {
|
|
189
|
+
await file.close();
|
|
190
|
+
}
|
|
191
|
+
input.signal?.throwIfAborted();
|
|
192
|
+
await saveJson(join(directory, "snapshot.json"), manifest);
|
|
193
|
+
record.phase = "succeeded";
|
|
194
|
+
record.message = "\u5907\u4EFD\u6210\u529F\uFF0C\u5B8C\u6574\u6027\u9A8C\u8BC1\u901A\u8FC7";
|
|
195
|
+
record.updatedAt = Date.now();
|
|
196
|
+
record.manifest = manifest;
|
|
197
|
+
await this.record(record).catch(() => {
|
|
198
|
+
});
|
|
199
|
+
input.progress?.({ ...record });
|
|
200
|
+
return manifest;
|
|
201
|
+
} catch (error) {
|
|
202
|
+
record.phase = input.signal?.aborted ? "cancelled" : "failed";
|
|
203
|
+
record.message = input.signal?.aborted ? "\u5907\u4EFD\u5DF2\u53D6\u6D88\uFF0C\u672A\u751F\u6210\u53EF\u6062\u590D\u5FEB\u7167\u3002" : backupFailure(error);
|
|
204
|
+
record.updatedAt = Date.now();
|
|
205
|
+
await this.record(record).catch(() => {
|
|
206
|
+
});
|
|
207
|
+
input.progress?.({ ...record });
|
|
208
|
+
throw new AppError("BACKUP_FAILED", record.message, 500);
|
|
209
|
+
} finally {
|
|
210
|
+
sqlite?.close();
|
|
211
|
+
await release?.();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
checkDatabase(directory, manifest) {
|
|
215
|
+
const sqlite = new this.options.Sqlite(join(directory, "app.sqlite"), {
|
|
216
|
+
readonly: true,
|
|
217
|
+
fileMustExist: true
|
|
218
|
+
});
|
|
219
|
+
try {
|
|
220
|
+
if (sqlite.pragma("integrity_check", { simple: true }) !== "ok" || sqlite.pragma("foreign_key_check").length)
|
|
221
|
+
throw new Error("Invalid backup database");
|
|
222
|
+
const history = inspectDatabase(sqlite, this.options.migrations);
|
|
223
|
+
requireRestorable(history.schemaVersion, this.options.contract);
|
|
224
|
+
if (history.schemaVersion !== manifest.schemaVersion || JSON.stringify(history.migrationHashes) !== JSON.stringify(manifest.migrationHashes))
|
|
225
|
+
throw new Error("Backup database differs from manifest");
|
|
226
|
+
} finally {
|
|
227
|
+
sqlite.close();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async verify(id) {
|
|
231
|
+
const directory = this.path(id);
|
|
232
|
+
if (!(await lstat(directory)).isDirectory() || (await lstat(directory)).isSymbolicLink())
|
|
233
|
+
throw new Error("Invalid backup directory");
|
|
234
|
+
const manifest = backupManifestSchema.parse(await readJson(join(directory, "snapshot.json")));
|
|
235
|
+
if (manifest.id !== id) throw new Error("Backup identity mismatch");
|
|
236
|
+
for (const file of manifest.files) {
|
|
237
|
+
const actual = await fingerprint(join(directory, file.name));
|
|
238
|
+
if (actual.size !== file.size || actual.sha256 !== file.sha256)
|
|
239
|
+
throw new Error("Backup checksum mismatch");
|
|
240
|
+
}
|
|
241
|
+
parseFileConfig(JSON.parse(await readFile(join(directory, "config.json"), "utf8")));
|
|
242
|
+
this.checkDatabase(directory, manifest);
|
|
243
|
+
return manifest;
|
|
244
|
+
}
|
|
245
|
+
/** Caller must own the instance lock, and must not have resumed business writes. */
|
|
246
|
+
async restoreFiles(id) {
|
|
247
|
+
await this.verify(id);
|
|
248
|
+
for (const name of ["app.sqlite-wal", "app.sqlite-shm"])
|
|
249
|
+
await rm(join(this.options.dataDir, name), { force: true });
|
|
250
|
+
await copyDurable(join(this.path(id), "app.sqlite"), join(this.options.dataDir, "app.sqlite"));
|
|
251
|
+
await chmod(join(this.options.dataDir, "app.sqlite"), 384);
|
|
252
|
+
await copyDurable(join(this.path(id), "config.json"), this.options.config);
|
|
253
|
+
await chmod(this.options.config, 384);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async function verifyLegacySnapshot(directory, Sqlite, migrationsFolder) {
|
|
257
|
+
z.object({ complete: z.literal(true) }).strict().parse(await readJson(join(directory, "snapshot.json")));
|
|
258
|
+
parseFileConfig(JSON.parse(await readFile(join(directory, "config.json"), "utf8")));
|
|
259
|
+
const db = new Sqlite(join(directory, "app.sqlite"), { readonly: true, fileMustExist: true });
|
|
260
|
+
try {
|
|
261
|
+
if (db.pragma("integrity_check", { simple: true }) !== "ok" || db.pragma("foreign_key_check").length)
|
|
262
|
+
throw new Error("\u65E7\u5907\u4EFD\u6570\u636E\u5E93\u635F\u574F");
|
|
263
|
+
inspectDatabase(db, readMigrations(migrationsFolder));
|
|
264
|
+
} finally {
|
|
265
|
+
db.close();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
export {
|
|
269
|
+
BackupStore,
|
|
270
|
+
backupDirectory,
|
|
271
|
+
backupFailure,
|
|
272
|
+
verifyLegacySnapshot
|
|
273
|
+
};
|
|
274
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/backups/store.ts"],
|
|
4
|
+
"sourcesContent": ["import { z, ZodError } from 'zod';\nimport { createHash, randomUUID } from 'node:crypto';\nimport { createReadStream } from 'node:fs';\nimport { chmod, lstat, mkdir, open, readFile, readdir, rm } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport type Database from 'better-sqlite3';\nimport lockfile from 'proper-lockfile';\nimport {\n backupIdSchema,\n backupManifestSchema,\n backupRecordSchema,\n type BackupManifest,\n type BackupRecord,\n} from '../shared/backups.js';\nimport { AppError } from '../shared/errors.js';\nimport type { DatabaseCompatibility } from '../shared/database.js';\nimport {\n inspectDatabase,\n readMigrations,\n requireRestorable,\n type Migration,\n} from '../server/db/history.js';\nimport { MAX_CONFIG_BYTES } from '../server/config-store.js';\nimport { parseConfig, parseFileConfig } from '../server/config.js';\nimport { copyDurable, readJson, saveJson } from '../updates/files.js';\n\nexport interface BackupOptions {\n directory: string;\n config: string;\n dataDir: string;\n appVersion: string;\n commit?: string;\n contract: DatabaseCompatibility;\n migrations: Migration[];\n Sqlite: typeof Database;\n}\nexport const backupDirectory = (home: string, dataDir: string) =>\n join(home, 'backups', createHash('sha256').update(resolve(dataDir)).digest('hex').slice(0, 16));\n\nasync function fingerprint(path: string) {\n const info = await lstat(path);\n if (!info.isFile() || info.isSymbolicLink())\n throw new Error('Backup contains a non-regular file');\n const hash = createHash('sha256');\n for await (const chunk of createReadStream(path)) hash.update(chunk);\n return { size: info.size, sha256: hash.digest('hex') };\n}\nexport function backupFailure(error: unknown) {\n if (error instanceof SyntaxError || error instanceof ZodError)\n return '\u670D\u52A1\u914D\u7F6E\u65E0\u6548\uFF0C\u5907\u4EFD\u672A\u5B8C\u6210\uFF1B\u8BF7\u68C0\u67E5\u914D\u7F6E\u540E\u91CD\u8BD5\u3002';\n const code = (error as NodeJS.ErrnoException)?.code;\n if (code === 'ENOSPC' || code === 'SQLITE_FULL') return '\u78C1\u76D8\u7A7A\u95F4\u4E0D\u8DB3\uFF0C\u5907\u4EFD\u672A\u5B8C\u6210\u3002';\n if (code === 'EACCES' || code === 'EPERM' || code === 'EROFS')\n return '\u65E0\u6CD5\u5199\u5165\u5907\u4EFD\u76EE\u5F55\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u6743\u9650\u3002';\n if (code === 'ELOCKED') return '\u914D\u7F6E\u6216\u5907\u4EFD\u76EE\u5F55\u6B63\u5728\u88AB\u5176\u4ED6\u64CD\u4F5C\u4F7F\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002';\n return '\u5907\u4EFD\u9A8C\u8BC1\u6216\u6587\u4EF6\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u78C1\u76D8\u3001\u6743\u9650\u53CA\u6587\u4EF6\u5B8C\u6574\u6027\u540E\u91CD\u8BD5\u3002';\n}\n\nexport class BackupStore {\n constructor(readonly options: BackupOptions) {}\n path(id: string) {\n return join(this.options.directory, backupIdSchema.parse(id));\n }\n async record(record: BackupRecord) {\n await saveJson(join(this.path(record.id), 'record.json'), record);\n }\n async list(): Promise<BackupRecord[]> {\n let names: string[];\n try {\n names = await readdir(this.options.directory);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw error;\n }\n const records: BackupRecord[] = [];\n for (const id of names.filter((name) => backupIdSchema.safeParse(name).success)) {\n try {\n const value = await readJson(join(this.path(id), 'record.json'));\n if (value === undefined) continue;\n const record = backupRecordSchema.parse(value);\n if (record.id !== id) throw new Error('Backup record identity mismatch');\n const parsed = backupManifestSchema.safeParse(\n await readJson(join(this.path(id), 'snapshot.json')),\n );\n if (parsed.success && parsed.data.id === id)\n records.push({\n ...record,\n path: this.path(id),\n phase: ['waiting', 'copying', 'verifying'].includes(record.phase)\n ? 'succeeded'\n : record.phase,\n manifest: parsed.data,\n });\n else\n records.push({\n ...record,\n path: this.path(id),\n phase: record.phase === 'succeeded' ? 'corrupt' : record.phase,\n manifest: undefined,\n });\n } catch {\n records.push({\n id,\n path: this.path(id),\n phase: 'corrupt',\n trigger: 'manual',\n createdAt: 0,\n updatedAt: 0,\n message: '\u5907\u4EFD\u8BB0\u5F55\u635F\u574F\uFF0C\u4E0D\u80FD\u6062\u590D\u3002',\n });\n }\n }\n return records.sort((a, b) => b.createdAt - a.createdAt);\n }\n async create(input: {\n id?: string;\n trigger: BackupManifest['trigger'];\n protectedBy?: string;\n signal?: AbortSignal;\n reserved?: boolean;\n progress?: (record: BackupRecord) => void;\n }): Promise<BackupManifest> {\n const { Sqlite, migrations, config, dataDir, appVersion, commit } = this.options;\n const id = backupIdSchema.parse(input.id ?? randomUUID());\n const directory = this.path(id);\n await mkdir(this.options.directory, { recursive: true, mode: 0o700 });\n if (\n !(await lstat(this.options.directory)).isDirectory() ||\n (await lstat(this.options.directory)).isSymbolicLink()\n )\n throw new Error('Invalid backup directory');\n await chmod(this.options.directory, 0o700);\n // Exclusive creation prevents overwriting a usable backup or replaying the same operation.\n if (input.reserved) {\n const reserved = await readJson<BackupRecord>(join(directory, 'record.json'));\n if (\n reserved?.id !== id ||\n reserved.phase !== 'waiting' ||\n (await readJson(join(directory, 'snapshot.json')))\n )\n throw new Error('Invalid backup reservation');\n } else await mkdir(directory, { mode: 0o700 });\n const record: BackupRecord = {\n id,\n phase: 'copying',\n trigger: input.trigger,\n createdAt: Date.now(),\n updatedAt: Date.now(),\n path: directory,\n message: '\u6B63\u5728\u751F\u6210\u6570\u636E\u5E93\u4E0E\u914D\u7F6E\u5FEB\u7167',\n };\n let release: (() => Promise<void>) | undefined;\n let sqlite: Database.Database | undefined;\n try {\n await this.record(record);\n input.progress?.({ ...record });\n input.signal?.throwIfAborted();\n release = await lockfile.lock(config, { realpath: false, retries: 0 });\n const configStat = await lstat(config);\n if (!configStat.isFile() || configStat.size > MAX_CONFIG_BYTES)\n throw new Error('Invalid config file');\n const contents = await readFile(config, 'utf8');\n if (parseConfig(JSON.parse(contents), config).dataDir !== resolve(dataDir))\n throw new Error('Saved configuration no longer identifies the running data directory');\n const configFile = await open(join(directory, 'config.json'), 'wx', 0o600);\n try {\n await configFile.writeFile(contents);\n await configFile.sync();\n } finally {\n await configFile.close();\n }\n sqlite = new Sqlite(join(dataDir, 'app.sqlite'), { readonly: true, fileMustExist: true });\n const history = inspectDatabase(sqlite, migrations);\n requireRestorable(history.schemaVersion, this.options.contract);\n await sqlite.backup(join(directory, 'app.sqlite'), {\n progress: ({ totalPages, remainingPages }) => {\n input.signal?.throwIfAborted();\n input.progress?.({\n ...record,\n progress: Math.round(((totalPages - remainingPages) / totalPages) * 100),\n });\n return 100;\n },\n });\n sqlite.close();\n sqlite = undefined;\n await chmod(join(directory, 'app.sqlite'), 0o600);\n if ((await readFile(config, 'utf8')) !== contents)\n throw new Error('Configuration changed externally during backup');\n input.signal?.throwIfAborted();\n record.phase = 'verifying';\n record.message = '\u6B63\u5728\u9A8C\u8BC1\u5907\u4EFD\u5B8C\u6574\u6027';\n record.updatedAt = Date.now();\n await this.record(record);\n input.progress?.({ ...record });\n const files = await Promise.all(\n (['app.sqlite', 'config.json'] as const).map(async (name) => ({\n name,\n ...(await fingerprint(join(directory, name))),\n })),\n );\n const manifest = backupManifestSchema.parse({\n formatVersion: 1,\n complete: true,\n id,\n createdAt: record.createdAt,\n appVersion,\n commit,\n trigger: input.trigger,\n configPath: config,\n dataDir,\n schemaVersion: history.schemaVersion,\n migrationHashes: history.migrationHashes,\n files,\n protectedBy: input.protectedBy,\n });\n this.checkDatabase(directory, manifest);\n // Make the copied SQLite durable before publishing the completion manifest.\n const file = await open(join(directory, 'app.sqlite'), 'r');\n try {\n await file.sync();\n } finally {\n await file.close();\n }\n input.signal?.throwIfAborted();\n await saveJson(join(directory, 'snapshot.json'), manifest);\n record.phase = 'succeeded';\n record.message = '\u5907\u4EFD\u6210\u529F\uFF0C\u5B8C\u6574\u6027\u9A8C\u8BC1\u901A\u8FC7';\n record.updatedAt = Date.now();\n record.manifest = manifest;\n await this.record(record).catch(() => {});\n input.progress?.({ ...record });\n return manifest;\n } catch (error) {\n record.phase = input.signal?.aborted ? 'cancelled' : 'failed';\n record.message = input.signal?.aborted\n ? '\u5907\u4EFD\u5DF2\u53D6\u6D88\uFF0C\u672A\u751F\u6210\u53EF\u6062\u590D\u5FEB\u7167\u3002'\n : backupFailure(error);\n record.updatedAt = Date.now();\n await this.record(record).catch(() => {});\n input.progress?.({ ...record });\n throw new AppError('BACKUP_FAILED', record.message, 500);\n } finally {\n sqlite?.close();\n await release?.();\n }\n }\n private checkDatabase(directory: string, manifest: BackupManifest) {\n const sqlite = new this.options.Sqlite(join(directory, 'app.sqlite'), {\n readonly: true,\n fileMustExist: true,\n });\n try {\n if (\n sqlite.pragma('integrity_check', { simple: true }) !== 'ok' ||\n (sqlite.pragma('foreign_key_check') as unknown[]).length\n )\n throw new Error('Invalid backup database');\n const history = inspectDatabase(sqlite, this.options.migrations);\n requireRestorable(history.schemaVersion, this.options.contract);\n if (\n history.schemaVersion !== manifest.schemaVersion ||\n JSON.stringify(history.migrationHashes) !== JSON.stringify(manifest.migrationHashes)\n )\n throw new Error('Backup database differs from manifest');\n } finally {\n sqlite.close();\n }\n }\n async verify(id: string): Promise<BackupManifest> {\n const directory = this.path(id);\n if (!(await lstat(directory)).isDirectory() || (await lstat(directory)).isSymbolicLink())\n throw new Error('Invalid backup directory');\n const manifest = backupManifestSchema.parse(await readJson(join(directory, 'snapshot.json')));\n if (manifest.id !== id) throw new Error('Backup identity mismatch');\n for (const file of manifest.files) {\n const actual = await fingerprint(join(directory, file.name));\n if (actual.size !== file.size || actual.sha256 !== file.sha256)\n throw new Error('Backup checksum mismatch');\n }\n parseFileConfig(JSON.parse(await readFile(join(directory, 'config.json'), 'utf8')));\n this.checkDatabase(directory, manifest);\n return manifest;\n }\n /** Caller must own the instance lock, and must not have resumed business writes. */\n async restoreFiles(id: string) {\n await this.verify(id);\n for (const name of ['app.sqlite-wal', 'app.sqlite-shm'])\n await rm(join(this.options.dataDir, name), { force: true });\n await copyDurable(join(this.path(id), 'app.sqlite'), join(this.options.dataDir, 'app.sqlite'));\n await chmod(join(this.options.dataDir, 'app.sqlite'), 0o600);\n await copyDurable(join(this.path(id), 'config.json'), this.options.config);\n await chmod(this.options.config, 0o600);\n }\n}\n\n/** Read-only verification for snapshots created before versioned manifests existed. */\nexport async function verifyLegacySnapshot(\n directory: string,\n Sqlite: typeof Database,\n migrationsFolder: string,\n) {\n z.object({ complete: z.literal(true) })\n .strict()\n .parse(await readJson(join(directory, 'snapshot.json')));\n parseFileConfig(JSON.parse(await readFile(join(directory, 'config.json'), 'utf8')));\n const db = new Sqlite(join(directory, 'app.sqlite'), { readonly: true, fileMustExist: true });\n try {\n if (\n db.pragma('integrity_check', { simple: true }) !== 'ok' ||\n (db.pragma('foreign_key_check') as unknown[]).length\n )\n throw new Error('\u65E7\u5907\u4EFD\u6570\u636E\u5E93\u635F\u574F');\n inspectDatabase(db, readMigrations(migrationsFolder));\n } finally {\n db.close();\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { setTimeout } from "node:timers/promises";
|
|
4
|
+
import { AppError } from "../shared/errors.js";
|
|
5
|
+
import { createBackupStore } from "../backups/factory.js";
|
|
6
|
+
import { acquireMaintenanceLock } from "../backups/lock.js";
|
|
7
|
+
import {
|
|
8
|
+
assertNoPendingRestore,
|
|
9
|
+
previewRestore,
|
|
10
|
+
repairRestore,
|
|
11
|
+
restoreBackup
|
|
12
|
+
} from "../backups/restore.js";
|
|
13
|
+
import { acquireInstanceLock } from "../server/instance-lock.js";
|
|
14
|
+
import { loadConfig } from "../server/config.js";
|
|
15
|
+
import { backupFailure } from "../backups/store.js";
|
|
16
|
+
import { control, isNotRunning } from "./control.js";
|
|
17
|
+
async function backupCommand(location, subcommand = "list", id, confirm, recoveryDataDir) {
|
|
18
|
+
const dataDir = recoveryDataDir ? resolve(recoveryDataDir) : (await loadConfig({ configPath: location.config, development: false })).dataDir;
|
|
19
|
+
const store = createBackupStore(location.config, dataDir);
|
|
20
|
+
try {
|
|
21
|
+
if (subcommand === "list")
|
|
22
|
+
return { directory: store.options.directory, records: await store.list() };
|
|
23
|
+
if (subcommand === "verify" && id) return await store.verify(id);
|
|
24
|
+
if (subcommand === "restore" && id) {
|
|
25
|
+
if (!confirm) return await previewRestore(store, id);
|
|
26
|
+
try {
|
|
27
|
+
await control(location, "status");
|
|
28
|
+
throw new AppError("RESTORE_RUNNING", "\u8BF7\u5148\u8FD0\u884C covro stop\uFF0C\u505C\u6B62\u5F53\u524D\u5B9E\u4F8B\u540E\u518D\u6062\u590D\u3002", 409);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (!isNotRunning(error)) throw error;
|
|
31
|
+
}
|
|
32
|
+
return await restoreBackup(store, id, confirm);
|
|
33
|
+
}
|
|
34
|
+
if (subcommand === "repair") return await repairRestore(store);
|
|
35
|
+
if (subcommand !== "create" || id)
|
|
36
|
+
throw new AppError(
|
|
37
|
+
"BACKUP_ARGUMENT",
|
|
38
|
+
"\u7528\u6CD5\uFF1Acovro backup create|list|verify ID|restore ID|repair",
|
|
39
|
+
400
|
|
40
|
+
);
|
|
41
|
+
const operation = randomUUID();
|
|
42
|
+
let running = false;
|
|
43
|
+
try {
|
|
44
|
+
await control(location, "status");
|
|
45
|
+
running = true;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (!isNotRunning(error)) throw error;
|
|
48
|
+
}
|
|
49
|
+
if (running) {
|
|
50
|
+
if (recoveryDataDir)
|
|
51
|
+
throw new AppError("BACKUP_ARGUMENT", "--data-dir \u4EC5\u7528\u4E8E\u505C\u670D\u540E\u7684\u6062\u590D\u6216\u5907\u4EFD\u3002");
|
|
52
|
+
let status = await control(location, "backup-create", {
|
|
53
|
+
clientOperationId: operation
|
|
54
|
+
});
|
|
55
|
+
for (; ; ) {
|
|
56
|
+
const record = status.records.find((entry) => entry.id === operation);
|
|
57
|
+
if (record && !["waiting", "copying", "verifying"].includes(record.phase)) {
|
|
58
|
+
if (record.phase !== "succeeded")
|
|
59
|
+
throw new AppError("BACKUP_FAILED", record.message, 500);
|
|
60
|
+
return record;
|
|
61
|
+
}
|
|
62
|
+
await setTimeout(500);
|
|
63
|
+
status = await control(location, "backup-status");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const release = await acquireInstanceLock(dataDir);
|
|
67
|
+
let releaseMaintenance;
|
|
68
|
+
try {
|
|
69
|
+
releaseMaintenance = await acquireMaintenanceLock(dataDir);
|
|
70
|
+
await assertNoPendingRestore(store.options.directory);
|
|
71
|
+
return await store.create({ id: operation, trigger: "manual" });
|
|
72
|
+
} finally {
|
|
73
|
+
await releaseMaintenance?.();
|
|
74
|
+
await release();
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (error instanceof AppError) throw error;
|
|
78
|
+
throw new AppError("BACKUP_FAILED", backupFailure(error), 500);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
backupCommand
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=backup.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/cli/backup.ts"],
|
|
4
|
+
"sourcesContent": ["import { randomUUID } from 'node:crypto';\nimport { resolve } from 'node:path';\nimport { setTimeout } from 'node:timers/promises';\nimport type { BackupStatus } from '../shared/backups.js';\nimport { AppError } from '../shared/errors.js';\nimport { createBackupStore } from '../backups/factory.js';\nimport { acquireMaintenanceLock } from '../backups/lock.js';\nimport {\n assertNoPendingRestore,\n previewRestore,\n repairRestore,\n restoreBackup,\n} from '../backups/restore.js';\nimport { acquireInstanceLock } from '../server/instance-lock.js';\nimport { loadConfig } from '../server/config.js';\nimport { backupFailure } from '../backups/store.js';\nimport { control, isNotRunning } from './control.js';\nimport type { CovroPaths } from './paths.js';\n\nexport async function backupCommand(\n location: CovroPaths,\n subcommand = 'list',\n id?: string,\n confirm?: string,\n recoveryDataDir?: string,\n) {\n const dataDir = recoveryDataDir\n ? resolve(recoveryDataDir)\n : (await loadConfig({ configPath: location.config, development: false })).dataDir;\n const store = createBackupStore(location.config, dataDir);\n try {\n if (subcommand === 'list')\n return { directory: store.options.directory, records: await store.list() };\n if (subcommand === 'verify' && id) return await store.verify(id);\n if (subcommand === 'restore' && id) {\n if (!confirm) return await previewRestore(store, id);\n try {\n await control(location, 'status');\n throw new AppError('RESTORE_RUNNING', '\u8BF7\u5148\u8FD0\u884C covro stop\uFF0C\u505C\u6B62\u5F53\u524D\u5B9E\u4F8B\u540E\u518D\u6062\u590D\u3002', 409);\n } catch (error) {\n if (!isNotRunning(error)) throw error;\n }\n return await restoreBackup(store, id, confirm);\n }\n if (subcommand === 'repair') return await repairRestore(store);\n if (subcommand !== 'create' || id)\n throw new AppError(\n 'BACKUP_ARGUMENT',\n '\u7528\u6CD5\uFF1Acovro backup create|list|verify ID|restore ID|repair',\n 400,\n );\n const operation = randomUUID();\n let running = false;\n try {\n await control(location, 'status');\n running = true;\n } catch (error) {\n if (!isNotRunning(error)) throw error;\n }\n if (running) {\n if (recoveryDataDir)\n throw new AppError('BACKUP_ARGUMENT', '--data-dir \u4EC5\u7528\u4E8E\u505C\u670D\u540E\u7684\u6062\u590D\u6216\u5907\u4EFD\u3002');\n let status = await control<BackupStatus>(location, 'backup-create', {\n clientOperationId: operation,\n });\n for (;;) {\n const record = status.records.find((entry) => entry.id === operation);\n if (record && !['waiting', 'copying', 'verifying'].includes(record.phase)) {\n if (record.phase !== 'succeeded')\n throw new AppError('BACKUP_FAILED', record.message, 500);\n return record;\n }\n await setTimeout(500);\n status = await control<BackupStatus>(location, 'backup-status');\n }\n }\n const release = await acquireInstanceLock(dataDir);\n let releaseMaintenance: (() => Promise<void>) | undefined;\n try {\n releaseMaintenance = await acquireMaintenanceLock(dataDir);\n await assertNoPendingRestore(store.options.directory);\n return await store.create({ id: operation, trigger: 'manual' });\n } finally {\n await releaseMaintenance?.();\n await release();\n }\n } catch (error) {\n if (error instanceof AppError) throw error;\n throw new AppError('BACKUP_FAILED', backupFailure(error), 500);\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,kBAAkB;AAE3B,SAAS,gBAAgB;AACzB,SAAS,yBAAyB;AAClC,SAAS,8BAA8B;AACvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,2BAA2B;AACpC,SAAS,kBAAkB;AAC3B,SAAS,qBAAqB;AAC9B,SAAS,SAAS,oBAAoB;AAGtC,eAAsB,cACpB,UACA,aAAa,QACb,IACA,SACA,iBACA;AACA,QAAM,UAAU,kBACZ,QAAQ,eAAe,KACtB,MAAM,WAAW,EAAE,YAAY,SAAS,QAAQ,aAAa,MAAM,CAAC,GAAG;AAC5E,QAAM,QAAQ,kBAAkB,SAAS,QAAQ,OAAO;AACxD,MAAI;AACF,QAAI,eAAe;AACjB,aAAO,EAAE,WAAW,MAAM,QAAQ,WAAW,SAAS,MAAM,MAAM,KAAK,EAAE;AAC3E,QAAI,eAAe,YAAY,GAAI,QAAO,MAAM,MAAM,OAAO,EAAE;AAC/D,QAAI,eAAe,aAAa,IAAI;AAClC,UAAI,CAAC,QAAS,QAAO,MAAM,eAAe,OAAO,EAAE;AACnD,UAAI;AACF,cAAM,QAAQ,UAAU,QAAQ;AAChC,cAAM,IAAI,SAAS,mBAAmB,+GAA+B,GAAG;AAAA,MAC1E,SAAS,OAAO;AACd,YAAI,CAAC,aAAa,KAAK,EAAG,OAAM;AAAA,MAClC;AACA,aAAO,MAAM,cAAc,OAAO,IAAI,OAAO;AAAA,IAC/C;AACA,QAAI,eAAe,SAAU,QAAO,MAAM,cAAc,KAAK;AAC7D,QAAI,eAAe,YAAY;AAC7B,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACF,UAAM,YAAY,WAAW;AAC7B,QAAI,UAAU;AACd,QAAI;AACF,YAAM,QAAQ,UAAU,QAAQ;AAChC,gBAAU;AAAA,IACZ,SAAS,OAAO;AACd,UAAI,CAAC,aAAa,KAAK,EAAG,OAAM;AAAA,IAClC;AACA,QAAI,SAAS;AACX,UAAI;AACF,cAAM,IAAI,SAAS,mBAAmB,2FAA0B;AAClE,UAAI,SAAS,MAAM,QAAsB,UAAU,iBAAiB;AAAA,QAClE,mBAAmB;AAAA,MACrB,CAAC;AACD,iBAAS;AACP,cAAM,SAAS,OAAO,QAAQ,KAAK,CAAC,UAAU,MAAM,OAAO,SAAS;AACpE,YAAI,UAAU,CAAC,CAAC,WAAW,WAAW,WAAW,EAAE,SAAS,OAAO,KAAK,GAAG;AACzE,cAAI,OAAO,UAAU;AACnB,kBAAM,IAAI,SAAS,iBAAiB,OAAO,SAAS,GAAG;AACzD,iBAAO;AAAA,QACT;AACA,cAAM,WAAW,GAAG;AACpB,iBAAS,MAAM,QAAsB,UAAU,eAAe;AAAA,MAChE;AAAA,IACF;AACA,UAAM,UAAU,MAAM,oBAAoB,OAAO;AACjD,QAAI;AACJ,QAAI;AACF,2BAAqB,MAAM,uBAAuB,OAAO;AACzD,YAAM,uBAAuB,MAAM,QAAQ,SAAS;AACpD,aAAO,MAAM,MAAM,OAAO,EAAE,IAAI,WAAW,SAAS,SAAS,CAAC;AAAA,IAChE,UAAE;AACA,YAAM,qBAAqB;AAC3B,YAAM,QAAQ;AAAA,IAChB;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAiB,SAAU,OAAM;AACrC,UAAM,IAAI,SAAS,iBAAiB,cAAc,KAAK,GAAG,GAAG;AAAA,EAC/D;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/cli/daemon.js
CHANGED
|
@@ -54,6 +54,13 @@ async function runDaemon(configPath) {
|
|
|
54
54
|
await updates.commitActivation(id);
|
|
55
55
|
runtime.service.setMaintenance(false);
|
|
56
56
|
response.end(JSON.stringify({ activated: true }));
|
|
57
|
+
} else if (request.url === "/backup-status") {
|
|
58
|
+
response.end(JSON.stringify(await runtime.backups?.status()));
|
|
59
|
+
} else if (request.url === "/backup-create") {
|
|
60
|
+
if (!runtime.backups) throw new Error("\u6B64\u5B9E\u4F8B\u672A\u914D\u7F6E\u5907\u4EFD");
|
|
61
|
+
response.end(
|
|
62
|
+
JSON.stringify(await runtime.backups.start(input))
|
|
63
|
+
);
|
|
57
64
|
} else if (request.url === "/update-status") {
|
|
58
65
|
response.end(JSON.stringify(await updates.status()));
|
|
59
66
|
} else if (request.url === "/update-check") {
|
package/dist/cli/daemon.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/cli/daemon.ts"],
|
|
4
|
-
"sourcesContent": ["import { createServer } from 'node:http';\nimport { chmod, mkdir, rm } from 'node:fs/promises';\nimport { acquireInstanceLock } from '../server/instance-lock.js';\nimport { loadConfig } from '../server/config.js';\nimport { startGateway } from '../server/main.js';\nimport { packageInfo, paths } from './paths.js';\nimport { UpdateManager } from '../updates/manager.js';\nimport { saveJson } from '../updates/files.js';\nimport { doctor, launchContext } from './doctor.js';\n\nexport async function runDaemon(configPath: string) {\n const location = paths(configPath);\n await mkdir(location.state, { recursive: true, mode: 0o700 });\n const release = await acquireInstanceLock(location.state);\n let config: Awaited<ReturnType<typeof loadConfig>>;\n let runtime: Awaited<ReturnType<typeof startGateway>> | undefined;\n let updates: UpdateManager | undefined;\n const context = await launchContext();\n const server = createServer((request, response) => {\n void (async () => {\n response.setHeader('content-type', 'application/json');\n if (request.method !== 'POST') throw new Error('Expected POST');\n let body = '';\n for await (const chunk of request) {\n body += String(chunk);\n if (body.length > 8192) throw new Error('Control request too large');\n }\n const input: unknown = body ? JSON.parse(body) : {};\n if (request.url === '/status') {\n response.end(\n JSON.stringify({\n version: packageInfo.version,\n pid: process.pid,\n origin: config.publicOrigin[0],\n configPath,\n dataDir: config.dataDir,\n maintenance: runtime?.service.isInMaintenance(),\n codexVersion: runtime?.codex.diagnostics().codexVersion ?? null,\n codexReady: runtime?.codex.ready ?? false,\n launchContext: context,\n }),\n );\n } else if (request.url === '/doctor') {\n response.end(JSON.stringify(await doctor()));\n } else if (request.url === '/stop') {\n await stop(() => response.end(JSON.stringify({ stopped: true })));\n } else if (request.url === '/prepare-update') {\n response.end(JSON.stringify(runtime!.service.prepareUpdate()));\n } else if (request.url === '/cancel-update') {\n runtime!.service.setMaintenance(false);\n response.end(JSON.stringify({ cancelled: true }));\n } else if (request.url === '/activate-update') {\n const id = (input as { id?: unknown }).id;\n if (typeof id !== 'string') throw new Error('Invalid upgrade identity');\n await updates!.commitActivation(id);\n runtime!.service.setMaintenance(false);\n response.end(JSON.stringify({ activated: true }));\n } else if (request.url === '/update-status') {\n response.end(JSON.stringify(await updates!.status()));\n } else if (request.url === '/update-check') {\n const channel = (input as { channel?: unknown }).channel;\n if (channel !== undefined && !['stable', 'beta', 'dev'].includes(String(channel)))\n throw new Error('Invalid channel');\n response.end(\n JSON.stringify(await updates!.check(channel as 'stable' | 'beta' | 'dev' | undefined)),\n );\n } else if (request.url === '/update') {\n response.end(\n JSON.stringify(await updates!.start(input as Parameters<UpdateManager['start']>[0])),\n );\n } else {\n response.statusCode = 404;\n response.end(JSON.stringify({ error: 'Unknown control action' }));\n }\n })().catch((error: unknown) => {\n response.statusCode = 400;\n response.end(\n JSON.stringify({ error: error instanceof Error ? error.message : 'Control failed' }),\n );\n });\n });\n let stopping: Promise<void> | undefined;\n const stop = (respond?: () => void) =>\n (stopping ??= (async () => {\n await runtime?.close();\n respond?.();\n await new Promise<void>((done) => server.close(() => done()));\n if (process.platform !== 'win32') await rm(location.socket, { force: true });\n await release();\n })());\n try {\n config = await loadConfig({ configPath, development: false });\n updates = new UpdateManager(\n location,\n config.dataDir,\n () => runtime?.codex.diagnostics().codexVersion ?? null,\n );\n await updates.initialize();\n const maintenance = await updates.activationPending();\n runtime = await startGateway(config, { updates, maintenance });\n if (process.platform !== 'win32') await rm(location.socket, { force: true });\n await new Promise<void>((done, reject) => {\n server.once('error', reject);\n server.listen(location.socket, done);\n });\n if (process.platform !== 'win32') await chmod(location.socket, 0o600);\n await saveJson(`${location.state}/instance.json`, {\n root: updates.context?.root,\n config: location.config,\n socket: location.socket,\n });\n console.log(`Covro ${packageInfo.version} listening on ${config.publicOrigin[0]}`);\n process.once('SIGINT', () => void stop());\n process.once('SIGTERM', () => void stop());\n } catch (error) {\n updates?.close();\n await runtime?.close();\n await release();\n throw error;\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { createServer } from 'node:http';\nimport { chmod, mkdir, rm } from 'node:fs/promises';\nimport { acquireInstanceLock } from '../server/instance-lock.js';\nimport { loadConfig } from '../server/config.js';\nimport { startGateway } from '../server/main.js';\nimport { packageInfo, paths } from './paths.js';\nimport { UpdateManager } from '../updates/manager.js';\nimport { saveJson } from '../updates/files.js';\nimport { doctor, launchContext } from './doctor.js';\n\nexport async function runDaemon(configPath: string) {\n const location = paths(configPath);\n await mkdir(location.state, { recursive: true, mode: 0o700 });\n const release = await acquireInstanceLock(location.state);\n let config: Awaited<ReturnType<typeof loadConfig>>;\n let runtime: Awaited<ReturnType<typeof startGateway>> | undefined;\n let updates: UpdateManager | undefined;\n const context = await launchContext();\n const server = createServer((request, response) => {\n void (async () => {\n response.setHeader('content-type', 'application/json');\n if (request.method !== 'POST') throw new Error('Expected POST');\n let body = '';\n for await (const chunk of request) {\n body += String(chunk);\n if (body.length > 8192) throw new Error('Control request too large');\n }\n const input: unknown = body ? JSON.parse(body) : {};\n if (request.url === '/status') {\n response.end(\n JSON.stringify({\n version: packageInfo.version,\n pid: process.pid,\n origin: config.publicOrigin[0],\n configPath,\n dataDir: config.dataDir,\n maintenance: runtime?.service.isInMaintenance(),\n codexVersion: runtime?.codex.diagnostics().codexVersion ?? null,\n codexReady: runtime?.codex.ready ?? false,\n launchContext: context,\n }),\n );\n } else if (request.url === '/doctor') {\n response.end(JSON.stringify(await doctor()));\n } else if (request.url === '/stop') {\n await stop(() => response.end(JSON.stringify({ stopped: true })));\n } else if (request.url === '/prepare-update') {\n response.end(JSON.stringify(runtime!.service.prepareUpdate()));\n } else if (request.url === '/cancel-update') {\n runtime!.service.setMaintenance(false);\n response.end(JSON.stringify({ cancelled: true }));\n } else if (request.url === '/activate-update') {\n const id = (input as { id?: unknown }).id;\n if (typeof id !== 'string') throw new Error('Invalid upgrade identity');\n await updates!.commitActivation(id);\n runtime!.service.setMaintenance(false);\n response.end(JSON.stringify({ activated: true }));\n } else if (request.url === '/backup-status') {\n response.end(JSON.stringify(await runtime!.backups?.status()));\n } else if (request.url === '/backup-create') {\n if (!runtime!.backups) throw new Error('\u6B64\u5B9E\u4F8B\u672A\u914D\u7F6E\u5907\u4EFD');\n response.end(\n JSON.stringify(await runtime!.backups.start(input as { clientOperationId: string })),\n );\n } else if (request.url === '/update-status') {\n response.end(JSON.stringify(await updates!.status()));\n } else if (request.url === '/update-check') {\n const channel = (input as { channel?: unknown }).channel;\n if (channel !== undefined && !['stable', 'beta', 'dev'].includes(String(channel)))\n throw new Error('Invalid channel');\n response.end(\n JSON.stringify(await updates!.check(channel as 'stable' | 'beta' | 'dev' | undefined)),\n );\n } else if (request.url === '/update') {\n response.end(\n JSON.stringify(await updates!.start(input as Parameters<UpdateManager['start']>[0])),\n );\n } else {\n response.statusCode = 404;\n response.end(JSON.stringify({ error: 'Unknown control action' }));\n }\n })().catch((error: unknown) => {\n response.statusCode = 400;\n response.end(\n JSON.stringify({ error: error instanceof Error ? error.message : 'Control failed' }),\n );\n });\n });\n let stopping: Promise<void> | undefined;\n const stop = (respond?: () => void) =>\n (stopping ??= (async () => {\n await runtime?.close();\n respond?.();\n await new Promise<void>((done) => server.close(() => done()));\n if (process.platform !== 'win32') await rm(location.socket, { force: true });\n await release();\n })());\n try {\n config = await loadConfig({ configPath, development: false });\n updates = new UpdateManager(\n location,\n config.dataDir,\n () => runtime?.codex.diagnostics().codexVersion ?? null,\n );\n await updates.initialize();\n const maintenance = await updates.activationPending();\n runtime = await startGateway(config, { updates, maintenance });\n if (process.platform !== 'win32') await rm(location.socket, { force: true });\n await new Promise<void>((done, reject) => {\n server.once('error', reject);\n server.listen(location.socket, done);\n });\n if (process.platform !== 'win32') await chmod(location.socket, 0o600);\n await saveJson(`${location.state}/instance.json`, {\n root: updates.context?.root,\n config: location.config,\n socket: location.socket,\n });\n console.log(`Covro ${packageInfo.version} listening on ${config.publicOrigin[0]}`);\n process.once('SIGINT', () => void stop());\n process.once('SIGTERM', () => void stop());\n } catch (error) {\n updates?.close();\n await runtime?.close();\n await release();\n throw error;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/cli/main.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { backupCommand } from "./backup.js";
|
|
1
2
|
import { open } from "node:fs/promises";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { control, isNotRunning } from "./control.js";
|
|
@@ -12,6 +13,8 @@ function parseArguments(args) {
|
|
|
12
13
|
const positional = [];
|
|
13
14
|
let configPath;
|
|
14
15
|
let channel;
|
|
16
|
+
let confirm;
|
|
17
|
+
let recoveryDataDir;
|
|
15
18
|
let allowDowngrade = false;
|
|
16
19
|
let allowUnverifiedCodex = false;
|
|
17
20
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -19,19 +22,27 @@ function parseArguments(args) {
|
|
|
19
22
|
if (arg === "--config" && args[i + 1]) configPath = args[++i];
|
|
20
23
|
else if (arg === "--channel" && ["stable", "beta", "dev"].includes(args[i + 1] ?? ""))
|
|
21
24
|
channel = args[++i];
|
|
25
|
+
else if (arg === "--confirm" && args[i + 1]) confirm = args[++i];
|
|
26
|
+
else if (arg === "--data-dir" && args[i + 1]) recoveryDataDir = args[++i];
|
|
22
27
|
else if (arg === "--allow-downgrade") allowDowngrade = true;
|
|
23
28
|
else if (arg === "--allow-unverified-codex") allowUnverifiedCodex = true;
|
|
24
29
|
else if (arg.startsWith("-") && arg !== "--help" && arg !== "--version")
|
|
25
30
|
throw new Error(`Unknown argument: ${arg}`);
|
|
26
31
|
else positional.push(arg);
|
|
27
32
|
}
|
|
28
|
-
const [command = "start", subcommand] = positional;
|
|
29
|
-
if (positional.length > (["service", "update"].includes(command) ? 2 : 1))
|
|
33
|
+
const [command = "start", subcommand, backupId] = positional;
|
|
34
|
+
if (positional.length > (command === "backup" ? 3 : ["service", "update"].includes(command) ? 2 : 1))
|
|
30
35
|
throw new Error("Too many arguments");
|
|
31
36
|
if ((channel || allowDowngrade || allowUnverifiedCodex) && command !== "update")
|
|
32
37
|
throw new Error("\u5347\u7EA7\u9009\u9879\u4EC5\u7528\u4E8E update");
|
|
38
|
+
if ((confirm || recoveryDataDir) && command !== "backup")
|
|
39
|
+
throw new Error("\u6062\u590D\u9009\u9879\u4EC5\u7528\u4E8E backup");
|
|
40
|
+
if (confirm && subcommand !== "restore") throw new Error("--confirm \u4EC5\u7528\u4E8E backup restore");
|
|
33
41
|
return {
|
|
34
42
|
command,
|
|
43
|
+
backupId,
|
|
44
|
+
confirm,
|
|
45
|
+
recoveryDataDir,
|
|
35
46
|
subcommand,
|
|
36
47
|
channel,
|
|
37
48
|
allowDowngrade,
|
|
@@ -49,18 +60,37 @@ async function startBackground(location) {
|
|
|
49
60
|
return startManagedBackground(location, join(packageRoot, "bin/covro.mjs"));
|
|
50
61
|
}
|
|
51
62
|
async function main(args = process.argv.slice(2)) {
|
|
52
|
-
const {
|
|
63
|
+
const {
|
|
64
|
+
command,
|
|
65
|
+
subcommand,
|
|
66
|
+
backupId,
|
|
67
|
+
confirm,
|
|
68
|
+
recoveryDataDir,
|
|
69
|
+
channel,
|
|
70
|
+
allowDowngrade,
|
|
71
|
+
allowUnverifiedCodex,
|
|
72
|
+
location
|
|
73
|
+
} = parseArguments(args);
|
|
53
74
|
if (command === "--version") {
|
|
54
75
|
console.log(packageInfo.version);
|
|
55
76
|
return;
|
|
56
77
|
}
|
|
57
78
|
if (command === "--help" || command === "help") {
|
|
58
79
|
console.log(
|
|
59
|
-
"Covro\n\ncovro [start|run|stop|status|doctor|logs] [--config FILE]\ncovro update [status|repair] [--channel stable|beta|dev] [--allow-downgrade] [--allow-unverified-codex]\ncovro service install|uninstall [--config FILE]\n\n\u9ED8\u8BA4\u540E\u53F0\u8FD0\u884C\uFF1Bservice install \u5355\u72EC\u542F\u7528\u767B\u5F55\u81EA\u542F\u3002doctor \u4ECE\u8FD0\u884C\u5B9E\u4F8B\u68C0\u67E5\u4F1A\u8BDD\u548C GitHub \u51ED\u8BC1\uFF0C\u4E0D\u663E\u793A\u4EE4\u724C\u3002"
|
|
80
|
+
"Covro\n\ncovro [start|run|stop|status|doctor|logs] [--config FILE]\ncovro update [status|repair] [--channel stable|beta|dev] [--allow-downgrade] [--allow-unverified-codex]\ncovro backup create|list|verify ID|restore ID|repair [--config FILE] [--confirm ID] [--data-dir DIR]\ncovro service install|uninstall [--config FILE]\n\n\u9ED8\u8BA4\u540E\u53F0\u8FD0\u884C\uFF1Bservice install \u5355\u72EC\u542F\u7528\u767B\u5F55\u81EA\u542F\u3002doctor \u4ECE\u8FD0\u884C\u5B9E\u4F8B\u68C0\u67E5\u4F1A\u8BDD\u548C GitHub \u51ED\u8BC1\uFF0C\u4E0D\u663E\u793A\u4EE4\u724C\u3002"
|
|
60
81
|
);
|
|
61
82
|
return;
|
|
62
83
|
}
|
|
63
|
-
if (command === "
|
|
84
|
+
if (command === "backup") {
|
|
85
|
+
console.log(
|
|
86
|
+
JSON.stringify(
|
|
87
|
+
await backupCommand(location, subcommand, backupId, confirm, recoveryDataDir),
|
|
88
|
+
null,
|
|
89
|
+
2
|
|
90
|
+
)
|
|
91
|
+
);
|
|
92
|
+
} else if (command === "start")
|
|
93
|
+
console.log(JSON.stringify(await startBackground(location), null, 2));
|
|
64
94
|
else if (command === "run") {
|
|
65
95
|
await ensureConfig(location);
|
|
66
96
|
await runDaemon(location.config);
|
package/dist/cli/main.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/cli/main.ts"],
|
|
4
|
-
"sourcesContent": ["import type { UpdateStatus } from '../shared/updates.js';\nimport { open } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { control, isNotRunning } from './control.js';\nimport { startBackground as startManagedBackground, stopBackground } from './background.js';\nimport { randomUUID } from 'node:crypto';\nimport { ensureConfig, packageInfo, packageRoot, paths, type CovroPaths } from './paths.js';\nimport { runDaemon } from './daemon.js';\nimport { service } from './service.js';\nimport { UpdateManager } from '../updates/manager.js';\nimport { loadConfig } from '../server/config.js';\n\nexport function parseArguments(args: string[]) {\n const positional: string[] = [];\n let configPath: string | undefined;\n let channel: 'stable' | 'beta' | 'dev' | undefined;\n let allowDowngrade = false;\n let allowUnverifiedCodex = false;\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (arg === '--config' && args[i + 1]) configPath = args[++i];\n else if (arg === '--channel' && ['stable', 'beta', 'dev'].includes(args[i + 1] ?? ''))\n channel = args[++i] as typeof channel;\n else if (arg === '--allow-downgrade') allowDowngrade = true;\n else if (arg === '--allow-unverified-codex') allowUnverifiedCodex = true;\n else if (arg.startsWith('-') && arg !== '--help' && arg !== '--version')\n throw new Error(`Unknown argument: ${arg}`);\n else positional.push(arg);\n }\n const [command = 'start', subcommand] = positional;\n if (positional.length > (['service', 'update'].includes(command) ? 2 : 1))\n throw new Error('Too many arguments');\n if ((channel || allowDowngrade || allowUnverifiedCodex) && command !== 'update')\n throw new Error('\u5347\u7EA7\u9009\u9879\u4EC5\u7528\u4E8E update');\n return {\n command,\n subcommand,\n channel,\n allowDowngrade,\n allowUnverifiedCodex,\n location: paths(configPath),\n };\n}\n\nexport async function startBackground(location: CovroPaths) {\n try {\n return await control(location, 'status');\n } catch (error) {\n if (!isNotRunning(error)) throw error;\n }\n await ensureConfig(location);\n return startManagedBackground(location, join(packageRoot, 'bin/covro.mjs'));\n}\n\nexport async function main(args = process.argv.slice(2)) {\n const {
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { backupCommand } from './backup.js';\nimport type { UpdateStatus } from '../shared/updates.js';\nimport { open } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { control, isNotRunning } from './control.js';\nimport { startBackground as startManagedBackground, stopBackground } from './background.js';\nimport { randomUUID } from 'node:crypto';\nimport { ensureConfig, packageInfo, packageRoot, paths, type CovroPaths } from './paths.js';\nimport { runDaemon } from './daemon.js';\nimport { service } from './service.js';\nimport { UpdateManager } from '../updates/manager.js';\nimport { loadConfig } from '../server/config.js';\n\nexport function parseArguments(args: string[]) {\n const positional: string[] = [];\n let configPath: string | undefined;\n let channel: 'stable' | 'beta' | 'dev' | undefined;\n let confirm: string | undefined;\n let recoveryDataDir: string | undefined;\n let allowDowngrade = false;\n let allowUnverifiedCodex = false;\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (arg === '--config' && args[i + 1]) configPath = args[++i];\n else if (arg === '--channel' && ['stable', 'beta', 'dev'].includes(args[i + 1] ?? ''))\n channel = args[++i] as typeof channel;\n else if (arg === '--confirm' && args[i + 1]) confirm = args[++i];\n else if (arg === '--data-dir' && args[i + 1]) recoveryDataDir = args[++i];\n else if (arg === '--allow-downgrade') allowDowngrade = true;\n else if (arg === '--allow-unverified-codex') allowUnverifiedCodex = true;\n else if (arg.startsWith('-') && arg !== '--help' && arg !== '--version')\n throw new Error(`Unknown argument: ${arg}`);\n else positional.push(arg);\n }\n const [command = 'start', subcommand, backupId] = positional;\n if (\n positional.length > (command === 'backup' ? 3 : ['service', 'update'].includes(command) ? 2 : 1)\n )\n throw new Error('Too many arguments');\n if ((channel || allowDowngrade || allowUnverifiedCodex) && command !== 'update')\n throw new Error('\u5347\u7EA7\u9009\u9879\u4EC5\u7528\u4E8E update');\n if ((confirm || recoveryDataDir) && command !== 'backup')\n throw new Error('\u6062\u590D\u9009\u9879\u4EC5\u7528\u4E8E backup');\n if (confirm && subcommand !== 'restore') throw new Error('--confirm \u4EC5\u7528\u4E8E backup restore');\n return {\n command,\n backupId,\n confirm,\n recoveryDataDir,\n subcommand,\n channel,\n allowDowngrade,\n allowUnverifiedCodex,\n location: paths(configPath),\n };\n}\n\nexport async function startBackground(location: CovroPaths) {\n try {\n return await control(location, 'status');\n } catch (error) {\n if (!isNotRunning(error)) throw error;\n }\n await ensureConfig(location);\n return startManagedBackground(location, join(packageRoot, 'bin/covro.mjs'));\n}\n\nexport async function main(args = process.argv.slice(2)) {\n const {\n command,\n subcommand,\n backupId,\n confirm,\n recoveryDataDir,\n channel,\n allowDowngrade,\n allowUnverifiedCodex,\n location,\n } = parseArguments(args);\n if (command === '--version') {\n console.log(packageInfo.version);\n return;\n }\n if (command === '--help' || command === 'help') {\n console.log(\n 'Covro\\n\\ncovro [start|run|stop|status|doctor|logs] [--config FILE]\\ncovro update [status|repair] [--channel stable|beta|dev] [--allow-downgrade] [--allow-unverified-codex]\\ncovro backup create|list|verify ID|restore ID|repair [--config FILE] [--confirm ID] [--data-dir DIR]\\ncovro service install|uninstall [--config FILE]\\n\\n\u9ED8\u8BA4\u540E\u53F0\u8FD0\u884C\uFF1Bservice install \u5355\u72EC\u542F\u7528\u767B\u5F55\u81EA\u542F\u3002doctor \u4ECE\u8FD0\u884C\u5B9E\u4F8B\u68C0\u67E5\u4F1A\u8BDD\u548C GitHub \u51ED\u8BC1\uFF0C\u4E0D\u663E\u793A\u4EE4\u724C\u3002',\n );\n return;\n }\n if (command === 'backup') {\n console.log(\n JSON.stringify(\n await backupCommand(location, subcommand, backupId, confirm, recoveryDataDir),\n null,\n 2,\n ),\n );\n } else if (command === 'start')\n console.log(JSON.stringify(await startBackground(location), null, 2));\n else if (command === 'run') {\n await ensureConfig(location);\n await runDaemon(location.config);\n } else if (command === 'update') {\n if (subcommand === 'repair') {\n const config = await loadConfig({ configPath: location.config, development: false });\n const manager = new UpdateManager(location, config.dataDir);\n console.log(JSON.stringify(await manager.repair(), null, 2));\n return;\n }\n if (subcommand && subcommand !== 'status') throw new Error('\u672A\u77E5\u5347\u7EA7\u547D\u4EE4');\n await startBackground(location);\n let codexConfirmation;\n if (allowUnverifiedCodex && !subcommand) {\n const checked = await control<UpdateStatus>(location, 'update-check', { channel });\n const candidate = checked.available;\n if (candidate?.codex.status === 'unverified' && candidate.codex.version)\n codexConfirmation = { version: candidate.codex.version, target: candidate.version };\n }\n console.log(\n JSON.stringify(\n await control(\n location,\n subcommand === 'status' ? 'update-check' : 'update',\n subcommand === 'status'\n ? { channel }\n : { clientOperationId: randomUUID(), channel, allowDowngrade, codexConfirmation },\n ),\n null,\n 2,\n ),\n );\n } else if (command === 'status' || command === 'stop' || command === 'doctor') {\n try {\n if (command === 'stop') {\n await stopBackground(location);\n console.log('Covro \u5DF2\u505C\u6B62');\n } else console.log(JSON.stringify(await control(location, command), null, 2));\n } catch (error) {\n if (!isNotRunning(error)) throw error;\n console.log('Covro \u672A\u8FD0\u884C');\n }\n } else if (command === 'logs') {\n const handle = await open(location.log, 'r');\n try {\n const { size } = await handle.stat();\n const buffer = Buffer.alloc(Math.min(size, 64 * 1024));\n await handle.read(buffer, 0, buffer.length, Math.max(0, size - buffer.length));\n console.log(buffer.toString('utf8'));\n } finally {\n await handle.close();\n }\n } else if (command === 'service' && ['install', 'uninstall'].includes(subcommand ?? '')) {\n await service(location, subcommand === 'install');\n console.log(subcommand === 'install' ? '\u5DF2\u542F\u7528 Covro \u767B\u5F55\u81EA\u542F' : '\u5DF2\u53D6\u6D88 Covro \u767B\u5F55\u81EA\u542F');\n } else throw new Error('\u672A\u77E5\u547D\u4EE4\uFF0C\u8FD0\u884C covro --help \u67E5\u770B\u7528\u6CD5');\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|