@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 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))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ddtcorex/dsh-maestro-supervisor",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Supervisor daemon for DSH Web resilience — auto-detect crashes, rollback to LKG, report",
5
5
  "type": "module",
6
6
  "bin": {