feat: 完整游戏运维功能

This commit is contained in:
npc0-hue
2026-07-18 09:04:01 +08:00
parent f3b14b7945
commit 48b8ad8d6c
187 changed files with 16607 additions and 1140 deletions
+141 -1
View File
@@ -36,6 +36,11 @@ export type RunCapability =
| "remote.run.db.sqlite.query"
| "remote.run.logs.transfer"
| "remote.run.rcon.command"
| "client-manager.deploy"
| "client-manager.control"
| "client-manager.update"
| "client-manager.rollback"
| "client-manager.uninstall"
| "artifacts.read"
| "artifacts.write"
| "ai.invoke";
@@ -145,6 +150,7 @@ export type PluginDependencyActionPayload = Record<string, string> & {
operation: "check" | "install";
probeKey?: string;
planKey?: string;
planDigest?: string;
idempotencyKey: string;
};
@@ -156,11 +162,26 @@ export type PluginLogBackfillPayload = Record<string, string> & {
};
export type PluginClientManagerPayload = Record<string, string> & {
operation: "generate" | "download" | "reset-key";
operation:
| "generate"
| "download"
| "reset-key"
| "status"
| "deploy"
| "start"
| "stop"
| "restart"
| "update"
| "rollback"
| "revoke-session"
| "retry"
| "uninstall";
profileKey: string;
targetOS?: RuntimePlatform;
targetArch?: RuntimeArch;
artifactId?: string;
installationId?: string;
expectedDeploymentGeneration?: string;
idempotencyKey: string;
};
@@ -243,6 +264,7 @@ export interface RuntimeTransportProfile {
export interface RuntimeClientManagerProfile {
key: string;
displayName?: string;
version?: string;
repository: {
url: string;
revisionPolicy: "pinned" | "branch" | "tag";
@@ -258,6 +280,36 @@ export interface RuntimeClientManagerProfile {
};
configTemplates?: Array<{ key: string; templateRef: string; outputRef: string }>;
outputArtifacts: string[];
deployment?: {
mode: "run-supervised";
executableRef: string;
arguments?: string[];
autoStart?: boolean;
requiredRunCapabilities: Array<"client-manager.deploy" | "client-manager.control" | "client-manager.update" | "client-manager.rollback" | "client-manager.uninstall">;
};
lifecycle?: {
actions: Array<"start" | "stop" | "restart" | "status" | "update" | "rollback" | "uninstall">;
startupTimeoutSeconds: number;
stopTimeoutSeconds: number;
};
health?: {
mode: "component-heartbeat" | "process";
intervalSeconds: number;
degradedAfterSeconds: number;
offlineAfterSeconds: number;
requiredCapabilities: Array<"component.register" | "component.heartbeat" | "component.health" | "component.control" | "game-client.bridge" | "logs.stream">;
};
compatibility?: {
minimumVersion?: string;
maximumVersion?: string;
allowDowngrade: boolean;
};
updatePolicy?: {
strategy: "manual-staged";
requireApproval: true;
healthConfirmationSeconds: number;
retainPrevious: true;
};
}
export interface GamePluginRuntimeProfiles {
@@ -283,6 +335,24 @@ export interface PluginArtifactReference {
storageBehavior?: string;
}
export interface PluginClientManagerLifecycleStatus {
installationId: string;
profileKey: string;
status: string;
phase?: string;
targetOS?: RuntimePlatform;
targetArch?: RuntimeArch;
version?: string;
previousVersion?: string;
artifactId?: string;
currentJobId?: string;
deploymentGeneration?: number;
health?: string;
healthReason?: string;
lastSeenAt?: string;
actions: string[];
}
export type PluginBridgeExecutionResponse<TResult extends Record<string, string> = Record<string, string>> = {
requestId: string;
pluginId: string;
@@ -315,6 +385,17 @@ export const pluginBridgeActionPolicies: Record<PluginBridgeAction, PluginBridge
export type PluginLifecycleAction = "install" | "start" | "stop" | "restart" | "status";
export interface PluginLifecycleActionDeclaration {
version: 1;
action: PluginLifecycleAction;
mode: "oneshot" | "supervised" | "control";
executableKey?: string;
arguments?: string[];
environment?: Record<`GAME_${string}` | `SERVER_${string}` | `RUN_${string}`, string>;
timeoutMs?: number;
stopTimeoutMs?: number;
}
export type GamePluginActions = Partial<Record<PluginLifecycleAction, string>> & {
install: string;
start: string;
@@ -507,8 +588,12 @@ export function createDependencyActionRequest(input: {
operation: PluginDependencyActionPayload["operation"];
probeKey?: string;
planKey?: string;
planDigest?: string;
idempotencyKey: string;
}): PluginBridgeExecutionRequest<PluginDependencyActionPayload> {
if (input.operation === "install" && !/^sha256:[a-fA-F0-9]{64}$/.test(input.planDigest ?? "")) {
throw new Error("dependency install requires the reviewed plan SHA-256 digest");
}
return createBridgeExecutionRequest({
requestId: input.requestId,
context: input.context,
@@ -517,6 +602,7 @@ export function createDependencyActionRequest(input: {
operation: input.operation,
probeKey: input.probeKey ?? "",
planKey: input.planKey ?? "",
planDigest: input.planDigest ?? "",
idempotencyKey: input.idempotencyKey
}
});
@@ -551,12 +637,16 @@ export function createClientManagerRequest(input: {
targetOS?: RuntimePlatform;
targetArch?: RuntimeArch;
artifactId?: string;
installationId?: string;
expectedDeploymentGeneration?: number;
idempotencyKey: string;
}): PluginBridgeExecutionRequest<PluginClientManagerPayload> {
const payload: PluginClientManagerPayload = {
operation: input.operation,
profileKey: input.profileKey,
artifactId: input.artifactId ?? "",
installationId: input.installationId ?? "",
expectedDeploymentGeneration: typeof input.expectedDeploymentGeneration === "number" ? String(input.expectedDeploymentGeneration) : "",
idempotencyKey: input.idempotencyKey
};
if (input.targetOS) {
@@ -605,6 +695,52 @@ export function parseArtifactReference(result: Record<string, string> | undefine
return reference;
}
export function parseClientManagerLifecycleStatus(result: Record<string, string> | undefined): PluginClientManagerLifecycleStatus | undefined {
if (!result) {
return undefined;
}
const deploymentGeneration = Number(result.deploymentGeneration ?? "0");
const values = [
result.installationId,
result.profileKey,
result.status,
result.phase,
result.targetOS,
result.targetArch,
result.version,
result.previousVersion,
result.artifactId,
result.currentJobId,
result.health,
result.healthReason,
result.lastSeenAt,
result.actions
];
if (!result.installationId || !result.profileKey || !result.status || !Number.isSafeInteger(deploymentGeneration) || deploymentGeneration < 0) {
return undefined;
}
if (values.some((value) => typeof value === "string" && containsUnsafeReferenceContent(value))) {
return undefined;
}
return {
installationId: result.installationId,
profileKey: result.profileKey,
status: result.status,
phase: result.phase,
targetOS: result.targetOS as RuntimePlatform | undefined,
targetArch: result.targetArch as RuntimeArch | undefined,
version: result.version,
previousVersion: result.previousVersion,
artifactId: result.artifactId,
currentJobId: result.currentJobId,
deploymentGeneration,
health: result.health,
healthReason: result.healthReason,
lastSeenAt: result.lastSeenAt,
actions: (result.actions ?? "").split(",").filter(Boolean)
};
}
export function parseBridgeExecutionResponse<TResult extends Record<string, string>>(response: PluginBridgeExecutionResponse<TResult>): PluginBridgeExecutionResponse<TResult> {
const safeError = response.error ? bridgeError(response.error.code, response.error.message, response.error.details ?? []) : undefined;
return {
@@ -672,6 +808,10 @@ function containsUnsafeReferenceContent(value: string): boolean {
lowered.includes("apikey=") ||
lowered.includes("storage://") ||
lowered.includes("file://") ||
lowered.includes("sessiontoken") ||
lowered.includes("secret://") ||
lowered.includes("hostpath") ||
lowered.includes("processid") ||
lowered.startsWith("sk-")
);
}