refactor: centralize platform infra ops helpers (#355)
Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
@@ -5,7 +5,7 @@ import pathPosix from "node:path/posix";
|
||||
import type { UniDeskConfig } from "./config";
|
||||
import { rootPath } from "./config";
|
||||
import { coreInternalFetch } from "./microservices";
|
||||
import { capture, compactCapture, parseJsonOutput, redactText, shQuote } from "./platform-infra-public-service";
|
||||
import { runSshCommandCapture, type SshCaptureResult } from "./ssh";
|
||||
|
||||
export interface OpsCommonOptions {
|
||||
targetId: string;
|
||||
@@ -24,6 +24,58 @@ export interface OpsCommandOptionSpec {
|
||||
flagOptions?: string[];
|
||||
}
|
||||
|
||||
export async function capture(config: UniDeskConfig, route: string, args: string[], stdin: string): Promise<SshCaptureResult> {
|
||||
return await runSshCommandCapture(config, route, args, stdin);
|
||||
}
|
||||
|
||||
export function parseJsonOutput(stdout: string): Record<string, unknown> | null {
|
||||
const trimmed = stdout.trim();
|
||||
if (trimmed.length === 0) return null;
|
||||
const start = trimmed.indexOf("{");
|
||||
const end = trimmed.lastIndexOf("}");
|
||||
if (start === -1 || end === -1 || end <= start) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed.slice(start, end + 1)) as unknown;
|
||||
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed as Record<string, unknown> : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function compactCapture(result: SshCaptureResult, options: { full?: boolean } = {}): Record<string, unknown> {
|
||||
const full = options.full ?? false;
|
||||
return {
|
||||
exitCode: result.exitCode,
|
||||
stdoutBytes: Buffer.byteLength(result.stdout, "utf8"),
|
||||
stderrBytes: Buffer.byteLength(result.stderr, "utf8"),
|
||||
stdoutTail: full || result.exitCode !== 0 ? redactText(result.stdout).slice(-8000) : "",
|
||||
stderrTail: full || result.exitCode !== 0 ? redactText(result.stderr).slice(-4000) : "",
|
||||
};
|
||||
}
|
||||
|
||||
export function redactText(text: string): string {
|
||||
return text
|
||||
.replace(/lbk_[A-Za-z0-9_-]+/gu, "lbk_<redacted>")
|
||||
.replace(/(postgres(?:ql)?:\/\/)[^@\s"']+@/giu, "$1<redacted>@")
|
||||
.replace(/(Bearer\s+)[A-Za-z0-9._~+/=-]+/giu, "$1<redacted>")
|
||||
.replace(/(["']?(?:N8N_ENCRYPTION_KEY|PASSWORD|SECRET|TOKEN|API[_-]?KEY|APIKEY|JWT[_-]?SECRET|DATABASE[_-]?URL)["']?\s*[:=]\s*["']?)[^"',\s}]+(["']?)/giu, "$1<redacted>$2");
|
||||
}
|
||||
|
||||
export function fingerprintValues(values: Record<string, string>, keys: string[]): string {
|
||||
const hash = createHash("sha256");
|
||||
for (const key of keys.slice().sort()) {
|
||||
hash.update(key);
|
||||
hash.update("\0");
|
||||
hash.update(values[key] ?? "");
|
||||
hash.update("\0");
|
||||
}
|
||||
return `sha256:${hash.digest("hex")}`;
|
||||
}
|
||||
|
||||
export function shQuote(value: string): string {
|
||||
return `'${value.replaceAll("'", "'\"'\"'")}'`;
|
||||
}
|
||||
|
||||
export function parseOpsCommonOptions(args: string[], spec: OpsCommandOptionSpec = {}): OpsCommonOptions & Record<string, string | boolean> {
|
||||
const stringOptions = new Set(["--target", ...(spec.stringOptions ?? [])]);
|
||||
const flagOptions = new Set(["--full", "--raw", ...(spec.flagOptions ?? [])]);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { Buffer } from "node:buffer";
|
||||
import type { UniDeskConfig } from "./config";
|
||||
import { applyPk01CaddyManagedBlock, caddyManagedBlockMarkers } from "./pk01-caddy";
|
||||
import { runSshCommandCapture, type SshCaptureResult } from "./ssh";
|
||||
import { capture, compactCapture, fingerprintValues, parseJsonOutput, redactText, shQuote } from "./platform-infra-ops-library";
|
||||
export { capture, compactCapture, fingerprintValues, parseJsonOutput, redactText, shQuote };
|
||||
|
||||
export interface PublicServiceExposure {
|
||||
enabled: boolean;
|
||||
@@ -48,10 +48,6 @@ export interface FrpcSecretMaterial {
|
||||
valuesPrinted: false;
|
||||
}
|
||||
|
||||
export async function capture(config: UniDeskConfig, route: string, args: string[], stdin: string): Promise<SshCaptureResult> {
|
||||
return await runSshCommandCapture(config, route, args, stdin);
|
||||
}
|
||||
|
||||
export async function applyPk01CaddyBlock(
|
||||
config: UniDeskConfig,
|
||||
serviceId: string,
|
||||
@@ -202,31 +198,6 @@ PY
|
||||
`;
|
||||
}
|
||||
|
||||
export function parseJsonOutput(stdout: string): Record<string, unknown> | null {
|
||||
const trimmed = stdout.trim();
|
||||
if (trimmed.length === 0) return null;
|
||||
const start = trimmed.indexOf("{");
|
||||
const end = trimmed.lastIndexOf("}");
|
||||
if (start === -1 || end === -1 || end <= start) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed.slice(start, end + 1)) as unknown;
|
||||
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed as Record<string, unknown> : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function compactCapture(result: SshCaptureResult, options: { full?: boolean } = {}): Record<string, unknown> {
|
||||
const full = options.full ?? false;
|
||||
return {
|
||||
exitCode: result.exitCode,
|
||||
stdoutBytes: Buffer.byteLength(result.stdout, "utf8"),
|
||||
stderrBytes: Buffer.byteLength(result.stderr, "utf8"),
|
||||
stdoutTail: full || result.exitCode !== 0 ? redactText(result.stdout).slice(-8000) : "",
|
||||
stderrTail: full || result.exitCode !== 0 ? redactText(result.stderr).slice(-4000) : "",
|
||||
};
|
||||
}
|
||||
|
||||
export function publicHttpProbe(baseUrl: string, path: string, options: { headers?: string[] } = {}): Record<string, unknown> {
|
||||
const url = `${baseUrl.replace(/\/+$/u, "")}${path}`;
|
||||
const args = ["-fsS", "--connect-timeout", "10", "--max-time", "30", "-o", "-", "-w", "\n%{http_code}"];
|
||||
@@ -254,29 +225,6 @@ export function publicHttpProbe(baseUrl: string, path: string, options: { header
|
||||
};
|
||||
}
|
||||
|
||||
export function redactText(text: string): string {
|
||||
return text
|
||||
.replace(/lbk_[A-Za-z0-9_-]+/gu, "lbk_<redacted>")
|
||||
.replace(/(postgres(?:ql)?:\/\/)[^@\s"']+@/giu, "$1<redacted>@")
|
||||
.replace(/(Bearer\s+)[A-Za-z0-9._~+/=-]+/giu, "$1<redacted>")
|
||||
.replace(/(["']?(?:N8N_ENCRYPTION_KEY|PASSWORD|SECRET|TOKEN|API[_-]?KEY|APIKEY|JWT[_-]?SECRET|DATABASE[_-]?URL)["']?\s*[:=]\s*["']?)[^"',\s}]+(["']?)/giu, "$1<redacted>$2");
|
||||
}
|
||||
|
||||
export function fingerprintValues(values: Record<string, string>, keys: string[]): string {
|
||||
const hash = createHash("sha256");
|
||||
for (const key of keys.slice().sort()) {
|
||||
hash.update(key);
|
||||
hash.update("\0");
|
||||
hash.update(values[key] ?? "");
|
||||
hash.update("\0");
|
||||
}
|
||||
return `sha256:${hash.digest("hex")}`;
|
||||
}
|
||||
|
||||
export function shQuote(value: string): string {
|
||||
return `'${value.replaceAll("'", "'\"'\"'")}'`;
|
||||
}
|
||||
|
||||
export function escapeTomlString(value: string): string {
|
||||
return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
|
||||
}
|
||||
|
||||
@@ -6,9 +6,8 @@ import { rootPath } from "./config";
|
||||
import { startJob } from "./jobs";
|
||||
import type { RenderedCliResult } from "./output";
|
||||
import { pk01CaddyMergeManagedBlocksPython, renderCaddyManagedBlock, renderSimpleReverseProxyCaddySiteBlock } from "./pk01-caddy";
|
||||
import { prepareFrpcSecret } from "./platform-infra-public-service";
|
||||
import { capture, compactCapture, parseJsonOutput, prepareFrpcSecret, shQuote } from "./platform-infra-public-service";
|
||||
import { fingerprintSecretValues, parseEnvFile, readEnvSourceFile, readTextFile, redactRepoPath, requiredEnvValue } from "./secrets";
|
||||
import { runSshCommandCapture, type SshCaptureResult } from "./ssh";
|
||||
|
||||
const serviceName = "sub2api";
|
||||
const fieldManager = "unidesk-platform-infra";
|
||||
@@ -2701,10 +2700,6 @@ function baseDomain(hostname: string): string {
|
||||
return parts.length <= 2 ? hostname : parts.slice(-2).join(".");
|
||||
}
|
||||
|
||||
function shQuote(value: string): string {
|
||||
return `'${value.replaceAll("'", "'\"'\"'")}'`;
|
||||
}
|
||||
|
||||
function secretRoot(sub2api: Sub2ApiConfig): string {
|
||||
const root = sub2api.runtime.secrets.root;
|
||||
return isAbsolute(root) ? root : rootPath(root);
|
||||
@@ -4150,24 +4145,6 @@ PY
|
||||
`;
|
||||
}
|
||||
|
||||
async function capture(config: UniDeskConfig, target: string, args: string[], input?: string): Promise<SshCaptureResult> {
|
||||
return await runSshCommandCapture(config, target, args, input);
|
||||
}
|
||||
|
||||
function parseJsonOutput(stdout: string): Record<string, unknown> | null {
|
||||
const trimmed = stdout.trim();
|
||||
if (trimmed.length === 0) return null;
|
||||
const start = trimmed.indexOf("{");
|
||||
const end = trimmed.lastIndexOf("}");
|
||||
if (start === -1 || end === -1 || end <= start) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed.slice(start, end + 1)) as unknown;
|
||||
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed as Record<string, unknown> : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function boolField(value: Record<string, unknown> | null, key: string, defaultValue: boolean): boolean {
|
||||
if (value === null) return defaultValue;
|
||||
const field = value[key];
|
||||
@@ -4177,14 +4154,3 @@ function boolField(value: Record<string, unknown> | null, key: string, defaultVa
|
||||
function asRecordOrNull(value: unknown): Record<string, unknown> | null {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
||||
}
|
||||
|
||||
function compactCapture(result: SshCaptureResult, options: { full?: boolean } = {}): Record<string, unknown> {
|
||||
const full = options.full ?? false;
|
||||
return {
|
||||
exitCode: result.exitCode,
|
||||
stdoutBytes: Buffer.byteLength(result.stdout, "utf8"),
|
||||
stderrBytes: Buffer.byteLength(result.stderr, "utf8"),
|
||||
stdoutTail: full || result.exitCode !== 0 ? result.stdout.slice(-8000) : "",
|
||||
stderrTail: full || result.exitCode !== 0 ? result.stderr.slice(-4000) : "",
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user