Merge pull request #257 from pikasTech/fix/d601-host-compose-status
fix: 降级 D601 host compose status 入口
This commit is contained in:
+8
-2
@@ -374,7 +374,10 @@ async function main(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
if (sub === "start") {
|
||||
emitJson(commandName, startStack(config));
|
||||
const result = startStack(config);
|
||||
const ok = (result as { ok?: unknown }).ok !== false;
|
||||
emitJson(commandName, result, ok);
|
||||
if (!ok) process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (sub === "stop") {
|
||||
@@ -382,7 +385,10 @@ async function main(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
if (sub === "status") {
|
||||
emitJson(commandName, await stackStatus(config));
|
||||
const result = await stackStatus(config);
|
||||
const ok = (result as { ok?: unknown }).ok !== false;
|
||||
emitJson(commandName, result, ok);
|
||||
if (!ok) process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (sub === "swap") {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { readConfig } from "./src/config";
|
||||
import { deprecatedD601HostComposeEntryForTest, type ComposeRuntimeEnv, type ContainerStatus } from "./src/docker";
|
||||
|
||||
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
|
||||
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
|
||||
}
|
||||
|
||||
const runtimeEnv: ComposeRuntimeEnv = {
|
||||
envFile: "/home/ubuntu/workspace/unidesk-dev/.state/docker-compose.env",
|
||||
logDir: "not-created-on-deprecated-d601-host-compose-entry",
|
||||
logDay: "20260610",
|
||||
logPrefix: "20260610_120000",
|
||||
};
|
||||
|
||||
const config = readConfig();
|
||||
|
||||
const deprecated = deprecatedD601HostComposeEntryForTest(config, runtimeEnv, [], "/home/ubuntu/workspace/unidesk-dev");
|
||||
assertCondition(deprecated?.ok === false, "D601 host compose entry should be a controlled failure", deprecated);
|
||||
assertCondition(deprecated.error === "deprecated-host-compose-entry", "D601 host compose entry should expose the deprecation classifier", deprecated);
|
||||
assertCondition(deprecated.runnerDisposition === "business-failed", "D601 host compose entry should be classified as business-failed", deprecated);
|
||||
assertCondition(deprecated.decision.hostComposeRetained === false, "D601 host compose entry should be explicitly deprecated", deprecated.decision);
|
||||
assertCondition(deprecated.correctEntrypoints.d601NativeK3s.includes("trans D601:k3s"), "D601 status should point to native k3s as the D601 acceptance entry", deprecated.correctEntrypoints);
|
||||
assertCondition(deprecated.correctEntrypoints.mainServerStatus.includes("/root/unidesk"), "D601 status should point main-server compose checks at /root/unidesk", deprecated.correctEntrypoints);
|
||||
assertCondition(deprecated.evidence.logDir === runtimeEnv.logDir, "D601 status should preserve repo-local runtime evidence", deprecated.evidence);
|
||||
assertCondition(!JSON.stringify(deprecated).includes("/workspace/unidesk/logs"), "D601 status should not surface the stale /workspace/unidesk/logs permission path", deprecated);
|
||||
assertCondition(deprecated.evidence.conflictingListeners.every((item) => item.expected === "ignored-on-deprecated-d601-host-compose-entry"), "D601 status should mark occupied public ports as ignored for this deprecated entry", deprecated.evidence.conflictingListeners);
|
||||
|
||||
const legacyOperatorPath = deprecatedD601HostComposeEntryForTest(config, runtimeEnv, [], "/home/ubuntu/unidesk");
|
||||
assertCondition(legacyOperatorPath?.error === "deprecated-host-compose-entry", "legacy D601 operator checkout should receive the same deprecation classifier", legacyOperatorPath);
|
||||
|
||||
const staleWorkspacePath = deprecatedD601HostComposeEntryForTest(config, runtimeEnv, [], "/workspace/unidesk");
|
||||
assertCondition(staleWorkspacePath?.error === "deprecated-host-compose-entry", "stale /workspace D601 checkout should be classified before log directory writes", staleWorkspacePath);
|
||||
assertCondition(!JSON.stringify(staleWorkspacePath).includes("/workspace/unidesk/logs"), "stale /workspace D601 checkout should not surface the stale log permission path", staleWorkspacePath);
|
||||
|
||||
const canonicalMainServer = deprecatedD601HostComposeEntryForTest(config, runtimeEnv, [], "/root/unidesk");
|
||||
assertCondition(canonicalMainServer === null, "canonical main-server checkout should keep the normal compose path", canonicalMainServer);
|
||||
|
||||
const existingContainer: ContainerStatus = {
|
||||
id: "abc123",
|
||||
name: "unidesk-backend-core",
|
||||
image: "unidesk/backend-core:test",
|
||||
status: "Up",
|
||||
ports: "",
|
||||
};
|
||||
const activeCompose = deprecatedD601HostComposeEntryForTest(config, runtimeEnv, [existingContainer], "/home/ubuntu/workspace/unidesk-dev");
|
||||
assertCondition(activeCompose === null, "D601 checkout with compose containers should keep the normal compose path", activeCompose);
|
||||
|
||||
console.log(JSON.stringify({ ok: true, contract: "d601-host-compose-status" }, null, 2));
|
||||
+92
-2
@@ -20,6 +20,29 @@ export interface ContainerStatus {
|
||||
ports: string;
|
||||
}
|
||||
|
||||
export interface DeprecatedHostComposeEntry {
|
||||
ok: false;
|
||||
error: "deprecated-host-compose-entry";
|
||||
runnerDisposition: "business-failed";
|
||||
hostRole: "d601-provider-host";
|
||||
composeProject: string;
|
||||
cwd: string;
|
||||
message: string;
|
||||
decision: {
|
||||
hostComposeRetained: false;
|
||||
disposition: "deprecated";
|
||||
reason: string;
|
||||
};
|
||||
evidence: {
|
||||
containers: ContainerStatus[];
|
||||
publicPorts: Array<{ name: string; port: number; listening: boolean }>;
|
||||
conflictingListeners: Array<{ name: string; port: number; listening: boolean; expected: string }>;
|
||||
logDir: string;
|
||||
};
|
||||
correctEntrypoints: Record<string, string>;
|
||||
references: string[];
|
||||
}
|
||||
|
||||
const rebuildableServices = ["backend-core", "frontend", "dev-frontend-proxy", "provider-gateway", "todo-note", "project-manager", "baidu-netdisk", "oa-event-flow", "code-queue-mgr"] as const;
|
||||
export type RebuildableService = typeof rebuildableServices[number];
|
||||
|
||||
@@ -103,6 +126,16 @@ function envValue(value: string): string {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function composeRuntimeEnvPreview(config: UniDeskConfig): ComposeRuntimeEnv {
|
||||
const parts = localDateParts(new Date());
|
||||
return {
|
||||
envFile: join(rootPath(config.paths.stateDir), "docker-compose.env"),
|
||||
logDir: "not-created-on-deprecated-d601-host-compose-entry",
|
||||
logDay: parts.day,
|
||||
logPrefix: parts.stamp,
|
||||
};
|
||||
}
|
||||
|
||||
export function writeComposeEnv(config: UniDeskConfig, freshLogPrefix: boolean): ComposeRuntimeEnv {
|
||||
const stateDir = rootPath(config.paths.stateDir);
|
||||
mkdirSync(stateDir, { recursive: true });
|
||||
@@ -290,9 +323,11 @@ export function composeConfig(config: UniDeskConfig): { runtimeEnv: ComposeRunti
|
||||
}
|
||||
|
||||
export function startStack(config: UniDeskConfig): unknown {
|
||||
const containers = dockerContainers(config);
|
||||
const deprecatedEntry = deprecatedD601HostComposeEntry(config, composeRuntimeEnvPreview(config), containers);
|
||||
if (deprecatedEntry !== null) return deprecatedEntry;
|
||||
const runtimeEnv = writeComposeEnv(config, true);
|
||||
const compose = resolveComposeCommand(config, runtimeEnv.envFile);
|
||||
const containers = dockerContainers(config);
|
||||
const occupiedPorts = fixedPorts(config).filter((item) => item.listening);
|
||||
if (occupiedPorts.length > 0 && containers.length === 0) {
|
||||
throw new Error(`Fixed UniDesk port is occupied before start: ${occupiedPorts.map((p) => `${p.name}:${p.port}`).join(", ")}`);
|
||||
@@ -528,6 +563,9 @@ function dockerExec(config: UniDeskConfig, container: string, command: string[])
|
||||
}
|
||||
|
||||
export async function stackStatus(config: UniDeskConfig): Promise<unknown> {
|
||||
const containers = dockerContainers(config);
|
||||
const deprecatedEntry = deprecatedD601HostComposeEntry(config, composeRuntimeEnvPreview(config), containers);
|
||||
if (deprecatedEntry !== null) return deprecatedEntry;
|
||||
const runtimeEnv = writeComposeEnv(config, false);
|
||||
const runtimeRaw = existsSync(runtimeEnv.envFile) ? readFileSync(runtimeEnv.envFile, "utf8") : "";
|
||||
const runtimeValue = (key: string): string => runtimeRaw.match(new RegExp(`^${key}=(.*)$`, "m"))?.[1]?.replace(/^"|"$/g, "") ?? "";
|
||||
@@ -569,7 +607,7 @@ export async function stackStatus(config: UniDeskConfig): Promise<unknown> {
|
||||
{ name: "baidu-netdisk", containerPort: 4244, hostPort: null },
|
||||
{ name: "oa-event-flow", containerPort: 4255, hostPort: null },
|
||||
],
|
||||
containers: dockerContainers(config),
|
||||
containers,
|
||||
health: {
|
||||
core: coreHealth,
|
||||
frontend: await probe(`http://127.0.0.1:${config.network.frontend.port}/health`),
|
||||
@@ -590,6 +628,58 @@ export async function stackStatus(config: UniDeskConfig): Promise<unknown> {
|
||||
};
|
||||
}
|
||||
|
||||
export function deprecatedD601HostComposeEntryForTest(config: UniDeskConfig, runtimeEnv: ComposeRuntimeEnv, containers: ContainerStatus[], currentRoot: string): DeprecatedHostComposeEntry | null {
|
||||
return deprecatedD601HostComposeEntry(config, runtimeEnv, containers, currentRoot);
|
||||
}
|
||||
|
||||
function deprecatedD601HostComposeEntry(config: UniDeskConfig, runtimeEnv: ComposeRuntimeEnv, containers: ContainerStatus[], currentRoot = repoRoot): DeprecatedHostComposeEntry | null {
|
||||
const cwd = resolve(currentRoot);
|
||||
const d601Workspaces = ["/home/ubuntu/unidesk", "/home/ubuntu/workspace/unidesk-dev", "/workspace/unidesk"];
|
||||
const cwdLooksD601 = d601Workspaces.some((workspace) => cwd === workspace || cwd.startsWith(`${workspace}/.worktree/`));
|
||||
const upgradeRootLooksMainServer = config.providerGateway.upgrade.hostProjectRoot === "/root/unidesk";
|
||||
if (!cwdLooksD601 || !upgradeRootLooksMainServer || containers.length > 0) return null;
|
||||
const publicPorts = fixedPorts(config);
|
||||
const conflictingListeners = publicPorts
|
||||
.filter((item) => item.listening)
|
||||
.map((item) => ({
|
||||
name: item.name,
|
||||
port: item.port,
|
||||
listening: item.listening,
|
||||
expected: "ignored-on-deprecated-d601-host-compose-entry",
|
||||
}));
|
||||
return {
|
||||
ok: false,
|
||||
error: "deprecated-host-compose-entry",
|
||||
runnerDisposition: "business-failed",
|
||||
hostRole: "d601-provider-host",
|
||||
composeProject: config.docker.projectName,
|
||||
cwd,
|
||||
message: "D601 host Compose is not the UniDesk server acceptance path. This command is deprecated here and does not start or validate the main-server stack.",
|
||||
decision: {
|
||||
hostComposeRetained: false,
|
||||
disposition: "deprecated",
|
||||
reason: "D601 owns provider-local Docker services and native k3s workloads; main-server Compose remains rooted at /root/unidesk on the main server.",
|
||||
},
|
||||
evidence: {
|
||||
containers,
|
||||
publicPorts,
|
||||
conflictingListeners,
|
||||
logDir: runtimeEnv.logDir,
|
||||
},
|
||||
correctEntrypoints: {
|
||||
mainServerStatus: "run bun scripts/cli.ts server status from /root/unidesk on the main server",
|
||||
d601NativeK3s: "trans D601:k3s kubectl get deploy,svc,pod,endpoints -n unidesk -o wide",
|
||||
decisionCenter: "bun scripts/cli.ts microservice health decision-center",
|
||||
d601DevDeploy: "bun scripts/cli.ts deploy plan --env dev --service <service>",
|
||||
},
|
||||
references: [
|
||||
"docs/reference/deployment.md#docker-compose-runtime",
|
||||
"docs/reference/dev-environment.md#d601-unidesk-workspace",
|
||||
"docs/reference/deploy.md#d601-native-k3s-emergency-guard",
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function listLogFiles(root: string): string[] {
|
||||
if (!existsSync(root)) return [];
|
||||
const entries = readdirSync(root, { withFileTypes: true });
|
||||
|
||||
+89
-20
@@ -595,6 +595,90 @@ function artifactRegistryHelp(): unknown {
|
||||
};
|
||||
}
|
||||
|
||||
function agentRunHelpSummary(): unknown {
|
||||
return {
|
||||
command: "agentrun v01 aipod-specs|queue|sessions|control-plane|git-mirror",
|
||||
output: "json",
|
||||
usage: [
|
||||
"bun scripts/cli.ts agentrun v01 aipod-specs show Artificer",
|
||||
"bun scripts/cli.ts agentrun v01 queue commander --reader-id cli",
|
||||
"bun scripts/cli.ts agentrun v01 sessions trace <sessionId> --after-seq 0 --limit 100",
|
||||
"bun scripts/cli.ts agentrun v01 control-plane status",
|
||||
],
|
||||
description: "Operate AgentRun v0.1 AipodSpec, queue, sessions, and G14 control-plane entrypoints.",
|
||||
};
|
||||
}
|
||||
|
||||
function platformInfraHelpSummary(): unknown {
|
||||
return {
|
||||
command: "platform-infra sub2api plan|apply|status|validate|codex-pool",
|
||||
output: "json",
|
||||
usage: [
|
||||
"bun scripts/cli.ts platform-infra sub2api plan",
|
||||
"bun scripts/cli.ts platform-infra sub2api status [--full|--raw]",
|
||||
"bun scripts/cli.ts platform-infra sub2api codex-pool validate",
|
||||
],
|
||||
description: "Operate G14 platform-infra services such as Sub2API and the YAML-controlled Codex pool.",
|
||||
};
|
||||
}
|
||||
|
||||
function hwlabNodeHelpSummary(): unknown {
|
||||
return {
|
||||
command: "hwlab nodes control-plane|git-mirror|secret --node <node> --lane <lane>",
|
||||
output: "json",
|
||||
usage: [
|
||||
"bun scripts/cli.ts hwlab nodes control-plane status --node G14 --lane v03",
|
||||
"bun scripts/cli.ts hwlab nodes git-mirror status --node G14 --lane v03",
|
||||
"bun scripts/cli.ts hwlab nodes secret status --node G14 --lane v03 --name <secret>",
|
||||
],
|
||||
description: "Operate HWLAB node/lane runtime prerequisites with node and lane passed as data.",
|
||||
};
|
||||
}
|
||||
|
||||
function hwlabG14HelpSummary(): unknown {
|
||||
return {
|
||||
command: "hwlab g14 monitor-prs|control-plane|git-mirror|tools-image|retirement",
|
||||
output: "json",
|
||||
usage: [
|
||||
"bun scripts/cli.ts hwlab g14 control-plane status --lane v02",
|
||||
"bun scripts/cli.ts hwlab g14 trigger-current --lane v02 --dry-run",
|
||||
"bun scripts/cli.ts hwlab g14 monitor-prs --status",
|
||||
],
|
||||
description: "Operate the G14 HWLAB runtime lane control-plane and legacy retirement helpers.",
|
||||
};
|
||||
}
|
||||
|
||||
function hwlabHelpSummary(): unknown {
|
||||
return {
|
||||
command: "hwlab g14|nodes|cd",
|
||||
output: "json",
|
||||
usage: [
|
||||
"bun scripts/cli.ts hwlab g14 control-plane status --lane v02",
|
||||
"bun scripts/cli.ts hwlab nodes control-plane status --node G14 --lane v03",
|
||||
"bun scripts/cli.ts hwlab cd audit --env dev",
|
||||
],
|
||||
description: "HWLAB operations. Current runtime work uses G14 lane commands; D601 cd is legacy diagnostics only.",
|
||||
};
|
||||
}
|
||||
|
||||
function helpFallback(help: unknown, error: unknown): unknown {
|
||||
if (typeof help !== "object" || help === null || Array.isArray(help)) return help;
|
||||
return {
|
||||
...help,
|
||||
degraded: true,
|
||||
degradedReason: "help-module-load-failed",
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
|
||||
async function loadHelp(loader: () => Promise<unknown>, fallback: unknown): Promise<unknown> {
|
||||
try {
|
||||
return await loader();
|
||||
} catch (error) {
|
||||
return helpFallback(fallback, error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function staticNamespaceHelp(args: string[]): Promise<unknown | null> {
|
||||
const [top, sub] = args;
|
||||
if (!args.slice(1).some(isHelpToken)) return null;
|
||||
@@ -614,25 +698,10 @@ export async function staticNamespaceHelp(args: string[]): Promise<unknown | nul
|
||||
if (top === "artifact-registry") return artifactRegistryHelp();
|
||||
if (top === "auth-broker") return authBrokerHelp();
|
||||
if (top === "gh") return ghHelp();
|
||||
if (top === "agentrun") {
|
||||
const { agentRunHelp } = await import("./agentrun");
|
||||
return agentRunHelp();
|
||||
}
|
||||
if (top === "platform-infra") {
|
||||
const { platformInfraHelp } = await import("./platform-infra");
|
||||
return platformInfraHelp();
|
||||
}
|
||||
if (top === "hwlab" && (sub === "node" || sub === "nodes")) {
|
||||
const { hwlabNodeHelp } = await import("./hwlab-node");
|
||||
return hwlabNodeHelp();
|
||||
}
|
||||
if (top === "hwlab" && sub === "g14") {
|
||||
const { hwlabG14Help } = await import("./hwlab-g14");
|
||||
return hwlabG14Help();
|
||||
}
|
||||
if (top === "hwlab") {
|
||||
const { hwlabHelp } = await import("./hwlab-cd");
|
||||
return hwlabHelp();
|
||||
}
|
||||
if (top === "agentrun") return loadHelp(async () => (await import("./agentrun")).agentRunHelp(), agentRunHelpSummary());
|
||||
if (top === "platform-infra") return loadHelp(async () => (await import("./platform-infra")).platformInfraHelp(), platformInfraHelpSummary());
|
||||
if (top === "hwlab" && (sub === "node" || sub === "nodes")) return loadHelp(async () => (await import("./hwlab-node")).hwlabNodeHelp(), hwlabNodeHelpSummary());
|
||||
if (top === "hwlab" && sub === "g14") return loadHelp(async () => (await import("./hwlab-g14")).hwlabG14Help(), hwlabG14HelpSummary());
|
||||
if (top === "hwlab") return loadHelp(async () => (await import("./hwlab-cd")).hwlabHelp(), hwlabHelpSummary());
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user