mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-10 04:32:34 +09:00
63 lines
2.2 KiB
TypeScript
63 lines
2.2 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import {Readable} from 'node:stream';
|
|
import * as v8 from 'node:v8';
|
|
import {recordAdminWrite} from '@app/api/admin/AdminAuditRecorder';
|
|
import {requireAdminACL} from '@app/api/middleware/AdminMiddleware';
|
|
import {RateLimitMiddleware} from '@app/api/middleware/RateLimitMiddleware';
|
|
import {OpenAPI} from '@app/api/middleware/ResponseTypeMiddleware';
|
|
import {RateLimitConfigs} from '@app/api/RateLimitConfig';
|
|
import type {HonoApp} from '@app/api/types/HonoEnv';
|
|
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
|
|
import {HeapSnapshotResponse} from '@fluxer/schema/src/domains/admin/AdminSchemas';
|
|
|
|
export function SystemAdminController(app: HonoApp) {
|
|
app.post(
|
|
'/admin/system/heap-snapshots',
|
|
RateLimitMiddleware(RateLimitConfigs.ADMIN_SYSTEM_HEAP_SNAPSHOT),
|
|
requireAdminACL(AdminACLs.SYSTEM_HEAP_SNAPSHOT),
|
|
OpenAPI({
|
|
operationId: 'create_admin_system_heap_snapshot',
|
|
summary: 'Create a V8 heap snapshot',
|
|
description:
|
|
'Writes a V8 heap snapshot of the current process and returns the snapshot file. Used for diagnosing memory leaks. Requires SYSTEM_HEAP_SNAPSHOT permission.',
|
|
responseSchema: HeapSnapshotResponse,
|
|
responseContentType: 'application/octet-stream',
|
|
statusCode: 200,
|
|
security: 'adminApiKey',
|
|
tags: 'Admin',
|
|
}),
|
|
async (ctx) => {
|
|
const snapshotPath = path.join('/tmp', `heap-${Date.now()}.heapsnapshot`);
|
|
try {
|
|
v8.writeHeapSnapshot(snapshotPath);
|
|
const stat = fs.statSync(snapshotPath);
|
|
await recordAdminWrite(ctx, {
|
|
targetType: 'system',
|
|
targetId: 0n,
|
|
action: 'create_heap_snapshot',
|
|
metadata: {size_bytes: stat.size},
|
|
});
|
|
const nodeStream = fs.createReadStream(snapshotPath);
|
|
const body = Readable.toWeb(nodeStream) as ReadableStream;
|
|
nodeStream.on('close', () => {
|
|
fs.unlink(snapshotPath, () => {});
|
|
});
|
|
return new Response(body, {
|
|
status: 200,
|
|
headers: {
|
|
'Content-Type': 'application/octet-stream',
|
|
'Content-Disposition': `attachment; filename="${path.basename(snapshotPath)}"`,
|
|
'Content-Length': String(stat.size),
|
|
},
|
|
});
|
|
} catch (error) {
|
|
fs.unlink(snapshotPath, () => {});
|
|
throw error;
|
|
}
|
|
},
|
|
);
|
|
}
|