@ddtcorex/dsh-maestro-supervisor 0.5.3 → 0.5.4
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/lib/snapshot.d.ts +3 -0
- package/lib/snapshot.js +128 -2
- package/package.json +1 -1
package/lib/snapshot.d.ts
CHANGED
|
@@ -10,6 +10,9 @@ export declare function writeLKG(dshHome: string, lkgRoot: string): Promise<{
|
|
|
10
10
|
ts: string;
|
|
11
11
|
manifest: Manifest;
|
|
12
12
|
}>;
|
|
13
|
+
export declare function pruneByAge(root: string, maxAgeMs: number): Promise<void>;
|
|
14
|
+
export declare function pruneBySize(root: string, maxBytes: number): Promise<void>;
|
|
15
|
+
export declare function isDuplicateLKG(dshHome: string, lkgRoot: string): Promise<boolean>;
|
|
13
16
|
export declare function verifyLKG(lkgPath: string): Promise<boolean>;
|
|
14
17
|
export declare function rotateLKG(lkgRoot: string, keep?: number): Promise<void>;
|
|
15
18
|
export declare function writeFailed(dshHome: string, failedRoot: string): Promise<{
|
package/lib/snapshot.js
CHANGED
|
@@ -17,12 +17,29 @@ function walkFiles(dir, base = dir) {
|
|
|
17
17
|
return out;
|
|
18
18
|
}
|
|
19
19
|
export async function writeLKG(dshHome, lkgRoot) {
|
|
20
|
+
// Dedupe: skip snapshot if current state identical to latest LKG (prevents 5-min unconditional growth)
|
|
21
|
+
try {
|
|
22
|
+
if (await isDuplicateLKG(dshHome, lkgRoot)) {
|
|
23
|
+
const entries = fs.readdirSync(lkgRoot).filter((n) => {
|
|
24
|
+
try {
|
|
25
|
+
return fs.statSync(path.join(lkgRoot, n)).isDirectory();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}).sort();
|
|
31
|
+
const latest = entries[entries.length - 1];
|
|
32
|
+
const manifestPath = path.join(lkgRoot, latest, 'manifest.json');
|
|
33
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
34
|
+
return { ts: latest, manifest };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch { }
|
|
20
38
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
21
39
|
const dest = path.join(lkgRoot, ts);
|
|
22
40
|
fs.mkdirSync(dest, { recursive: true });
|
|
23
|
-
// Copy DSH home contents (if exists, copy recursively)
|
|
41
|
+
// Copy DSH home contents (if exists, copy recursively) — skip .supervisor to avoid recursion
|
|
24
42
|
if (fs.existsSync(dshHome)) {
|
|
25
|
-
// Use cpSync if available
|
|
26
43
|
for (const entry of fs.readdirSync(dshHome)) {
|
|
27
44
|
if (entry === '.supervisor')
|
|
28
45
|
continue;
|
|
@@ -39,8 +56,117 @@ export async function writeLKG(dshHome, lkgRoot) {
|
|
|
39
56
|
.map(f => ({ path: f, sha256: sha256File(path.join(dest, f)) })),
|
|
40
57
|
};
|
|
41
58
|
fs.writeFileSync(path.join(dest, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
59
|
+
// Retention: keep only 3 most recent, plus age (7d) and size (5GB) caps — prevents unbounded 40GB+ growth
|
|
60
|
+
await rotateLKG(lkgRoot, 3).catch(() => { });
|
|
61
|
+
await pruneByAge(lkgRoot, 7 * 24 * 60 * 60 * 1000).catch(() => { });
|
|
62
|
+
await pruneBySize(lkgRoot, 5 * 1024 * 1024 * 1024).catch(() => { });
|
|
42
63
|
return { ts, manifest };
|
|
43
64
|
}
|
|
65
|
+
export async function pruneByAge(root, maxAgeMs) {
|
|
66
|
+
if (!fs.existsSync(root))
|
|
67
|
+
return;
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
const entries = fs.readdirSync(root).filter((n) => {
|
|
70
|
+
try {
|
|
71
|
+
return fs.statSync(path.join(root, n)).isDirectory();
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
for (const name of entries) {
|
|
78
|
+
try {
|
|
79
|
+
const tsStr = name.replace(/^(\d{4})-(\d{2})-(\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d+)Z$/, '$1-$2-$3T$4:$5:$6.$7Z');
|
|
80
|
+
const ts = Date.parse(tsStr);
|
|
81
|
+
if (!isNaN(ts) && now - ts > maxAgeMs) {
|
|
82
|
+
fs.rmSync(path.join(root, name), { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch { }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export async function pruneBySize(root, maxBytes) {
|
|
89
|
+
if (!fs.existsSync(root))
|
|
90
|
+
return;
|
|
91
|
+
const entries = fs.readdirSync(root).filter((n) => {
|
|
92
|
+
try {
|
|
93
|
+
return fs.statSync(path.join(root, n)).isDirectory();
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}).sort();
|
|
99
|
+
let total = 0;
|
|
100
|
+
const sizes = [];
|
|
101
|
+
for (const name of entries) {
|
|
102
|
+
try {
|
|
103
|
+
const p = path.join(root, name);
|
|
104
|
+
let size = 0;
|
|
105
|
+
for (const f of walkFiles(p)) {
|
|
106
|
+
try {
|
|
107
|
+
size += fs.statSync(path.join(p, f)).size;
|
|
108
|
+
}
|
|
109
|
+
catch { }
|
|
110
|
+
}
|
|
111
|
+
sizes.push({ name, size });
|
|
112
|
+
total += size;
|
|
113
|
+
}
|
|
114
|
+
catch { }
|
|
115
|
+
}
|
|
116
|
+
for (const { name, size } of sizes) {
|
|
117
|
+
if (total <= maxBytes)
|
|
118
|
+
break;
|
|
119
|
+
try {
|
|
120
|
+
fs.rmSync(path.join(root, name), { recursive: true, force: true });
|
|
121
|
+
total -= size;
|
|
122
|
+
}
|
|
123
|
+
catch { }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export async function isDuplicateLKG(dshHome, lkgRoot) {
|
|
127
|
+
// Lightweight dedupe: if latest snapshot is <5 minutes old, skip (prevents 5-min unconditional growth)
|
|
128
|
+
// Full hash check is too heavy (would read 500MB+ each tick) and caused status timeouts
|
|
129
|
+
if (!fs.existsSync(lkgRoot))
|
|
130
|
+
return false;
|
|
131
|
+
const entries = fs.readdirSync(lkgRoot).filter((n) => {
|
|
132
|
+
try {
|
|
133
|
+
return fs.statSync(path.join(lkgRoot, n)).isDirectory();
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}).sort();
|
|
139
|
+
if (!entries.length)
|
|
140
|
+
return false;
|
|
141
|
+
const latestName = entries[entries.length - 1];
|
|
142
|
+
try {
|
|
143
|
+
const tsStr = latestName.replace(/^(\d{4})-(\d{2})-(\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d+)Z$/, '$1-$2-$3T$4:$5:$6.$7Z');
|
|
144
|
+
const ts = Date.parse(tsStr);
|
|
145
|
+
if (!isNaN(ts) && Date.now() - ts < 5 * 60 * 1000) {
|
|
146
|
+
// If latest is recent and DSH home hasn't changed in mtime, consider duplicate
|
|
147
|
+
// Quick check: compare latest snapshot's mtime vs DSH home's newest file mtime
|
|
148
|
+
const latestPath = path.join(lkgRoot, latestName);
|
|
149
|
+
const latestMtime = fs.statSync(latestPath).mtimeMs;
|
|
150
|
+
let newestFileMtime = 0;
|
|
151
|
+
if (fs.existsSync(dshHome)) {
|
|
152
|
+
for (const entry of fs.readdirSync(dshHome)) {
|
|
153
|
+
if (entry === '.supervisor')
|
|
154
|
+
continue;
|
|
155
|
+
try {
|
|
156
|
+
const s = fs.statSync(path.join(dshHome, entry));
|
|
157
|
+
if (s.mtimeMs > newestFileMtime)
|
|
158
|
+
newestFileMtime = s.mtimeMs;
|
|
159
|
+
}
|
|
160
|
+
catch { }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (newestFileMtime > 0 && newestFileMtime < latestMtime)
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch { }
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
44
170
|
export async function verifyLKG(lkgPath) {
|
|
45
171
|
const manifestPath = path.join(lkgPath, 'manifest.json');
|
|
46
172
|
if (!fs.existsSync(manifestPath))
|