Skip to content

Commit 8cbc767

Browse files
committed
feat(core): added monitor folder content checksum reporting
1 parent 61aacd6 commit 8cbc767

File tree

6 files changed

+120
-3
lines changed

6 files changed

+120
-3
lines changed

core/checksumMonitorFolder.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//FIXME: after refactor, move to the correct path
2+
import bytes from 'bytes';
3+
import fs from 'node:fs/promises';
4+
import path from 'node:path';
5+
import { createHash } from 'node:crypto';
6+
import { txEnv } from './globalData';
7+
8+
//Hash test
9+
const hashFile = async (filePath: string) => {
10+
const rawFile = await fs.readFile(filePath, 'utf8')
11+
const normalized = rawFile.normalize('NFKC')
12+
.replace(/\r\n/g, '\n')
13+
.replace(/^\uFEFF/, '');
14+
return createHash('sha1').update(normalized).digest('hex');
15+
}
16+
17+
// Limits
18+
const MAX_FILES = 300;
19+
const MAX_TOTAL_SIZE = bytes('50MB');
20+
const MAX_FILE_SIZE = bytes('20MB');
21+
const MAX_DEPTH = 10;
22+
const MAX_EXECUTION_TIME = 30 * 1000;
23+
const IGNORED_FOLDERS = [
24+
'db',
25+
'cache',
26+
'dist',
27+
'.reports',
28+
'license_report',
29+
'tmp_core_tsc',
30+
'node_modules',
31+
'txData',
32+
];
33+
34+
35+
type ContentFileType = {
36+
path: string;
37+
size: number;
38+
hash: string;
39+
}
40+
41+
export default async function checksumMonitorFolder() {
42+
const rootPath = txEnv.txAdminResourcePath;
43+
const allFiles: ContentFileType[] = [];
44+
let totalFiles = 0;
45+
let totalSize = 0;
46+
47+
try {
48+
const tsStart = Date.now();
49+
const scanDir = async (dir: string, depth: number = 0) => {
50+
if (depth > MAX_DEPTH) {
51+
throw new Error('MAX_DEPTH');
52+
}
53+
54+
let filesFound = 0;
55+
const entries = await fs.readdir(dir, { withFileTypes: true });
56+
for (const entry of entries) {
57+
if (totalFiles >= MAX_FILES) {
58+
throw new Error('MAX_FILES');
59+
} else if (totalSize >= MAX_TOTAL_SIZE) {
60+
throw new Error('MAX_TOTAL_SIZE');
61+
} else if (Date.now() - tsStart > MAX_EXECUTION_TIME) {
62+
throw new Error('MAX_EXECUTION_TIME');
63+
}
64+
65+
const entryPath = path.join(dir, entry.name);
66+
let relativeEntryPath = path.relative(rootPath, entryPath);
67+
relativeEntryPath = './' + relativeEntryPath.split(path.sep).join(path.posix.sep);
68+
69+
if (entry.isDirectory()) {
70+
if (IGNORED_FOLDERS.includes(entry.name)) {
71+
continue;
72+
}
73+
await scanDir(entryPath, depth + 1);
74+
} else if (entry.isFile()) {
75+
const stats = await fs.stat(entryPath);
76+
if (stats.size > MAX_FILE_SIZE) {
77+
throw new Error('MAX_SIZE');
78+
}
79+
80+
allFiles.push({
81+
path: relativeEntryPath,
82+
size: stats.size,
83+
hash: await hashFile(entryPath),
84+
});
85+
filesFound++;
86+
totalFiles++;
87+
totalSize += stats.size;
88+
}
89+
}
90+
return filesFound;
91+
};
92+
await scanDir(rootPath);
93+
allFiles.sort((a, b) => a.path.localeCompare(b.path));
94+
return {
95+
totalFiles,
96+
totalSize,
97+
allFiles,
98+
};
99+
} catch (error) {
100+
//At least saving the progress
101+
return {
102+
error: (error as any).message,
103+
totalFiles,
104+
totalSize,
105+
allFiles,
106+
};
107+
}
108+
}

core/updateChangelog.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//FIXME: after efactor, move to the correct path (maybe drop the 'update' prefix?)
1+
//FIXME: after refactor, move to the correct path (maybe drop the 'update' prefix?)
22
const modulename = 'UpdateChecker';
33
import semver, { ReleaseType } from 'semver';
44
import { z } from "zod";

core/updateRollout.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//FIXME: after refactor, move to the correct path
12
import { it, expect, suite } from 'vitest';
23
import { getUpdateRolloutDelay } from './updateRollout';
34

core/updateRollout.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//FIXME: after efactor, move to the correct path
1+
//FIXME: after refactor, move to the correct path
22
import type { ReleaseType } from 'semver';
33

44
type RolloutStrategyType = {

core/webroutes/diagnostics/sendReport.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { getServerDataConfigs, getServerDataContent, ServerDataContentType, Serv
88
import MemCache from '@extras/MemCache';
99
import consoleFactory, { getLogBuffer } from '@extras/console';
1010
import { AuthedCtx } from '@core/components/WebServer/ctxTypes';
11+
import checksumMonitorFolder from '@core/checksumMonitorFolder';
1112
const console = consoleFactory(modulename);
1213

1314
//Consts & Helpers
@@ -111,6 +112,12 @@ export default async function SendDiagnosticsReport(ctx: AuthedCtx) {
111112
perfSvMain = ctx.txAdmin.statsManager.svRuntime.getServerPerfSummary();
112113
} catch (error) { }
113114

115+
//Monitor integrity check
116+
let monitorContent = undefined;
117+
try {
118+
monitorContent = await checksumMonitorFolder();
119+
} catch (error) { }
120+
114121
//Prepare report object
115122
const reportData = {
116123
$schemaVersion: 2,
@@ -128,6 +135,7 @@ export default async function SendDiagnosticsReport(ctx: AuthedCtx) {
128135
adminList,
129136
serverDataContent,
130137
cfgFiles,
138+
monitorContent,
131139
};
132140

133141
// //Preparing request

web/main/diagnostics.ejs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@
182182
<li>Player database statistics</li>
183183
<li>txAdmin settings (no bot token)</li>
184184
<li>List of admins (no passwords/hashes)</li>
185-
<li>List of files/folders in server data folder</li>
185+
<li>List of files/folders in server data and monitor folders</li>
186186
<li>Config files in server data folder</li>
187187
</ul>
188188
</li>

0 commit comments

Comments
 (0)