Files
HY2XS_flamy/orchestrator/src/commands/diagnostics.ts
T

97 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { join } from "node:path";
import type { CommonOptions } from "../types/context";
import { InvalidUtf8Error, readText, writeTextAtomic } from "../lib/fs";
import { info, setOperationContext } from "../lib/log";
import { runMutating, runReadOnlyArgvStatus } from "../lib/process";
import { redactEnv, redactLogText, redactYaml } from "../lib/redaction";
function shellEscapeSingleQuotes(value: string): string {
return value.replaceAll("'", "'\\''");
}
const unavailable = (reason: string): string =>
`[HY2XS: источник не включён в диагностику: ${reason}]\n`;
async function writeDiagnostic(outDir: string, name: string, content: string): Promise<void> {
await writeTextAtomic(join(outDir, name), content, { mode: 0o600, owner: "root", group: "root" });
}
/**
* Вывод сначала попадает в память, редактируется и лишь затем записывается.
* В staging-каталоге ни на мгновение нет сырого journal/systemctl-вывода с
* machine token.
*/
async function collectCommand(outDir: string, name: string, argv: string[]): Promise<void> {
try {
const result = await runReadOnlyArgvStatus(argv);
const combined = [result.stdout, result.stderr]
.filter(Boolean)
.join(result.stdout && result.stderr ? "\n" : "");
const status = result.exitCode === 0 ? "" : `[exit code: ${result.exitCode}]\n`;
const truncated = result.stdoutTruncated || result.stderrTruncated
? "[HY2XS: вывод обрезан по безопасному пределу 8 МиБ на поток]\n"
: "";
await writeDiagnostic(outDir, name, redactLogText(`${status}${truncated}${combined}`));
} catch (error) {
info(`diagnostics: ${name} пропущен: ${error instanceof Error ? error.message : String(error)}`);
await writeDiagnostic(outDir, name, unavailable("команда недоступна"));
}
}
/**
* Конфигурация никогда не копируется в staging как есть. Повреждённый UTF-8
* не декодируется с заменой: в бандл попадает только безопасное объяснение без
* исходных байтов.
*/
async function collectFile(
outDir: string,
name: string,
source: string,
sanitize: (content: string) => string
): Promise<void> {
await writeDiagnostic(outDir, name, await prepareDiagnosticFile(source, sanitize));
}
/** Чистая граница «прочитать строго -> санитизировать -> вернуть текст». */
export async function prepareDiagnosticFile(
source: string,
sanitize: (content: string) => string
): Promise<string> {
try {
const raw = await readText(source);
return sanitize(raw);
} catch (error) {
const reason = error instanceof InvalidUtf8Error ? "некорректный UTF-8" : "файл недоступен";
info(`diagnostics: ${source} пропущен: ${error instanceof Error ? error.message : String(error)}`);
return unavailable(reason);
}
}
export async function diagnosticsCollect(_options: CommonOptions): Promise<void> {
const opId = `diag-${Date.now().toString(36)}`;
setOperationContext(opId);
const outDir = `/var/log/hy2xs/diagnostics/${opId}`;
const archive = `/var/log/hy2xs/diagnostics/${opId}.tar.gz`;
await runMutating`install -d -m 0700 ${outDir}`;
await collectCommand(outDir, "systemd-hysteria.txt", ["systemctl", "status", "hysteria-server"]);
await collectCommand(outDir, "systemd-admin.txt", ["systemctl", "status", "hy2xs-admin"]);
await collectCommand(outDir, "journal-hysteria.log", ["journalctl", "-u", "hysteria-server", "-n", "300", "--no-pager"]);
await collectCommand(outDir, "journal-admin.log", ["journalctl", "-u", "hy2xs-admin", "-n", "300", "--no-pager"]);
await collectCommand(outDir, "nftables.ruleset", ["nft", "list", "ruleset"]);
await collectCommand(outDir, "uname.txt", ["uname", "-a"]);
await collectCommand(outDir, "ss-ltnup.txt", ["ss", "-ltnup"]);
await collectFile(outDir, "os-release.txt", "/etc/os-release", redactLogText);
await collectFile(outDir, "post-install.env", "/etc/hysteria/post-install.env", redactEnv);
await collectFile(outDir, "hy2xs.env", "/etc/hy2xs/hy2xs.env", redactEnv);
await collectFile(outDir, "hysteria-config.yaml", "/etc/hysteria/config.yaml", redactYaml);
await collectFile(outDir, "install-state.json", "/var/lib/hy2xs/install-state.json", redactLogText);
await runMutating`sh -c ${`umask 077; tar -czf '${shellEscapeSingleQuotes(archive)}' -C '${shellEscapeSingleQuotes(outDir)}' .`}`;
await runMutating`chmod 0600 ${archive}`;
info(`diagnostics bundle collected: ${archive}`);
}