feat: add D601 dev backend path
This commit is contained in:
+68
-18
@@ -21,7 +21,6 @@ const syntaxFiles = [
|
||||
"scripts/src/docker.ts",
|
||||
"scripts/src/e2e.ts",
|
||||
"scripts/src/remote.ts",
|
||||
"src/components/backend-core/src/index.ts",
|
||||
"src/components/frontend/src/index.ts",
|
||||
"src/components/frontend/src/app.tsx",
|
||||
"src/components/frontend/src/decision-center.tsx",
|
||||
@@ -40,6 +39,7 @@ export interface CheckOptions {
|
||||
components: boolean;
|
||||
compose: boolean;
|
||||
logs: boolean;
|
||||
rust: boolean;
|
||||
}
|
||||
|
||||
const defaultCheckOptions: CheckOptions = {
|
||||
@@ -49,8 +49,32 @@ const defaultCheckOptions: CheckOptions = {
|
||||
components: false,
|
||||
compose: false,
|
||||
logs: false,
|
||||
rust: false,
|
||||
};
|
||||
|
||||
export function checkHelp(): Record<string, unknown> {
|
||||
return {
|
||||
ok: true,
|
||||
command: "check",
|
||||
usage: "bun scripts/cli.ts check [--syntax-only|--full|--files|--scripts-typecheck|--components|--compose|--logs|--rust]",
|
||||
defaultMode: "syntax/config only; Rust is never compiled on the master server by default",
|
||||
options: [
|
||||
{ name: "--syntax-only|--basic", description: "Run only config validation, Bun version and TypeScript syntax transpile." },
|
||||
{ name: "--full", description: "Enable all local checks except Rust compilation." },
|
||||
{ name: "--files", description: "Verify required entrypoint files, including backend-core Cargo files." },
|
||||
{ name: "--scripts-typecheck", description: "Run scripts TypeScript typecheck." },
|
||||
{ name: "--components", description: "Run component TypeScript typecheck." },
|
||||
{ name: "--compose", description: "Render Docker Compose config." },
|
||||
{ name: "--logs", description: "Check unified log rotation policy." },
|
||||
{ name: "--rust", description: "Run cargo check only when UNIDESK_D601_RUST_CHECK=1 is set inside D601 CI/dev execution." },
|
||||
],
|
||||
rustBoundary: {
|
||||
masterServer: "do not run cargo check/build here",
|
||||
d601: "use deploy apply --env dev --service backend-core and CI with UNIDESK_D601_RUST_CHECK=1",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function parseCheckOptions(args: string[]): CheckOptions {
|
||||
const options = { ...defaultCheckOptions };
|
||||
for (const arg of args) {
|
||||
@@ -71,15 +95,10 @@ export function parseCheckOptions(args: string[]): CheckOptions {
|
||||
options.compose = true;
|
||||
} else if (arg === "--logs") {
|
||||
options.logs = true;
|
||||
} else if (arg === "--rust") {
|
||||
options.rust = true;
|
||||
} else if (arg === "--basic" || arg === "--syntax-only") {
|
||||
options.full = false;
|
||||
options.files = false;
|
||||
options.scriptsTypecheck = false;
|
||||
options.components = false;
|
||||
options.compose = false;
|
||||
options.logs = false;
|
||||
} else if (arg === "--help" || arg === "-h") {
|
||||
throw new Error("check usage: bun scripts/cli.ts check [--syntax-only|--full|--files|--scripts-typecheck|--components|--compose|--logs]");
|
||||
Object.assign(options, defaultCheckOptions);
|
||||
} else {
|
||||
throw new Error(`unknown check option: ${arg}`);
|
||||
}
|
||||
@@ -92,8 +111,8 @@ function fileItem(path: string): CheckItem {
|
||||
return { name: `file:${path}`, ok: existsSync(absolute), detail: absolute };
|
||||
}
|
||||
|
||||
function commandItem(name: string, command: string[], timeoutMs = 30_000): CheckItem {
|
||||
const result = runCommand(command, repoRoot, { timeoutMs });
|
||||
function commandItem(name: string, command: string[], timeoutMs = 30_000, env: NodeJS.ProcessEnv = process.env): CheckItem {
|
||||
const result = runCommand(command, repoRoot, { timeoutMs, env });
|
||||
return {
|
||||
name,
|
||||
ok: result.exitCode === 0,
|
||||
@@ -133,9 +152,9 @@ function syntaxItem(): CheckItem {
|
||||
|
||||
function unifiedLogRotationItem(): CheckItem {
|
||||
const serviceFiles = [
|
||||
"src/components/backend-core/src/logger.ts",
|
||||
"src/components/frontend/src/index.ts",
|
||||
"src/components/provider-gateway/src/index.ts",
|
||||
"src/components/microservices/code-queue-mgr/src/index.ts",
|
||||
"src/components/microservices/code-queue/src/index.ts",
|
||||
"src/components/microservices/k3sctl-adapter/src/index.ts",
|
||||
"src/components/microservices/mdtodo/src/index.ts",
|
||||
@@ -143,7 +162,6 @@ function unifiedLogRotationItem(): CheckItem {
|
||||
"src/components/microservices/baidu-netdisk/src/index.ts",
|
||||
"src/components/microservices/oa-event-flow/src/index.ts",
|
||||
"src/components/microservices/decision-center/src/index.ts",
|
||||
"src/components/microservices/code-queue-mgr/src/index.ts",
|
||||
];
|
||||
const offenders = serviceFiles.flatMap((path) => {
|
||||
const text = readFileSync(rootPath(path), "utf8");
|
||||
@@ -151,17 +169,41 @@ function unifiedLogRotationItem(): CheckItem {
|
||||
const missingWriter = !text.includes("createHourlyJsonlWriter");
|
||||
return directLogAppend || missingWriter ? [{ path, directLogAppend, missingWriter }] : [];
|
||||
});
|
||||
const backendLogger = readFileSync(rootPath("src/components/backend-core/src/logger.rs"), "utf8");
|
||||
const backendMissingRotation = !backendLogger.includes("current_path") || !backendLogger.includes("prune");
|
||||
const backendDirectUnboundedAppend = backendLogger.includes("appendFileSync");
|
||||
if (backendMissingRotation || backendDirectUnboundedAppend) {
|
||||
offenders.push({ path: "src/components/backend-core/src/logger.rs", directLogAppend: backendDirectUnboundedAppend, missingWriter: backendMissingRotation });
|
||||
}
|
||||
return {
|
||||
name: "logs:unified-hourly-rotation",
|
||||
ok: offenders.length === 0,
|
||||
detail: {
|
||||
sharedWriter: "src/components/shared/src/rotating-jsonl.ts",
|
||||
checkedFiles: serviceFiles,
|
||||
checkedFiles: ["src/components/backend-core/src/logger.rs", ...serviceFiles],
|
||||
offenders,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function rustCheckItem(): CheckItem {
|
||||
if (process.env.UNIDESK_D601_RUST_CHECK !== "1") {
|
||||
return {
|
||||
name: "rust:backend-core",
|
||||
ok: false,
|
||||
detail: {
|
||||
skipped: true,
|
||||
reason: "Rust compilation is intentionally not allowed on the master server; run it from D601 CI/dev deploy.",
|
||||
enableOnD601: "UNIDESK_D601_RUST_CHECK=1 bun scripts/cli.ts check --rust",
|
||||
deployPath: "bun scripts/cli.ts deploy apply --env dev --service backend-core",
|
||||
},
|
||||
};
|
||||
}
|
||||
const envPath = process.env.HOME ? `${process.env.HOME}/.cargo/bin:${process.env.PATH ?? ""}` : process.env.PATH;
|
||||
const env = envPath ? { ...process.env, PATH: envPath } : process.env;
|
||||
return commandItem("rust:backend-core", ["cargo", "check", "--manifest-path", "src/components/backend-core/Cargo.toml"], 180_000, env);
|
||||
}
|
||||
|
||||
function skippedItem(name: string, reason: string, enableWith: string): CheckItem {
|
||||
return { name, ok: true, detail: { skipped: true, reason, enableWith } };
|
||||
}
|
||||
@@ -178,7 +220,10 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
|
||||
fileItem("AGENTS.md"),
|
||||
fileItem("TEST.md"),
|
||||
fileItem("docker-compose.yml"),
|
||||
fileItem("src/components/backend-core/src/index.ts"),
|
||||
fileItem("src/components/backend-core/Cargo.toml"),
|
||||
fileItem("src/components/backend-core/Cargo.lock"),
|
||||
fileItem("src/components/backend-core/src/main.rs"),
|
||||
fileItem("src/components/backend-core/src/http.rs"),
|
||||
fileItem("src/components/frontend/src/index.ts"),
|
||||
fileItem("src/components/provider-gateway/src/index.ts"),
|
||||
fileItem("src/components/microservices/oa-event-flow/src/index.ts"),
|
||||
@@ -193,19 +238,19 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
|
||||
items.push(skippedItem("files:required-entrypoints", "required file presence scan is opt-in", "--files or --full"));
|
||||
}
|
||||
if (options.scriptsTypecheck) {
|
||||
items.push(commandItem("typescript:scripts", ["bunx", "tsc", "-p", "scripts/tsconfig.json", "--noEmit", "--pretty", "false"]));
|
||||
items.push(commandItem("typescript:scripts", ["bunx", "tsc", "-p", "scripts/tsconfig.json", "--noEmit", "--pretty", "false"], 120_000));
|
||||
} else {
|
||||
items.push(skippedItem("typescript:scripts", "scripts TypeScript typecheck is opt-in", "--scripts-typecheck or --full"));
|
||||
}
|
||||
if (options.logs) {
|
||||
items.push(unifiedLogRotationItem());
|
||||
} else {
|
||||
items.push(skippedItem("logs:unified-hourly-rotation", "heavy policy scan is opt-in", "--logs or --full"));
|
||||
items.push(skippedItem("logs:unified-hourly-rotation", "policy scan is opt-in", "--logs or --full"));
|
||||
}
|
||||
if (options.components) {
|
||||
items.push(commandItem("typescript:components", ["bunx", "tsc", "-p", "src/tsconfig.check.json", "--pretty", "false"], 180_000));
|
||||
} else {
|
||||
items.push(skippedItem("typescript:components", "full component TypeScript check is opt-in", "--components or --full"));
|
||||
items.push(skippedItem("typescript:components", "component TypeScript check is opt-in", "--components or --full"));
|
||||
}
|
||||
if (options.compose) {
|
||||
const compose = composeConfig(config);
|
||||
@@ -225,5 +270,10 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
|
||||
} else {
|
||||
items.push(skippedItem("docker-compose:config", "Docker Compose config rendering is opt-in", "--compose or --full"));
|
||||
}
|
||||
if (options.rust) {
|
||||
items.push(rustCheckItem());
|
||||
} else {
|
||||
items.push(skippedItem("rust:backend-core", "Rust check/build must run on D601 dev deploy or CI, not on the master server", "--rust inside D601 CI with UNIDESK_D601_RUST_CHECK=1"));
|
||||
}
|
||||
return { ok: items.every((item) => item.ok), mode: options.full ? "full" : "basic", options, items };
|
||||
}
|
||||
|
||||
+5
-1
@@ -104,6 +104,10 @@ function boolFlag(args: string[], name: string): boolean {
|
||||
return args.includes(name);
|
||||
}
|
||||
|
||||
function isHelpArg(value: string | undefined): boolean {
|
||||
return value === "help" || value === "--help" || value === "-h";
|
||||
}
|
||||
|
||||
function shellQuote(value: string): string {
|
||||
return `'${value.replace(/'/gu, "'\\''")}'`;
|
||||
}
|
||||
@@ -811,7 +815,7 @@ function requireRunId(value: string): string {
|
||||
|
||||
export async function runCiCommand(_config: UniDeskConfig, args: string[]): Promise<Record<string, unknown>> {
|
||||
const [action = "status", nameArg] = args;
|
||||
if (action === "help" || action === "--help" || action === "-h") return help();
|
||||
if (isHelpArg(action) || args.slice(1).some(isHelpArg)) return help();
|
||||
if (action === "install") return install();
|
||||
if (action === "status") return status();
|
||||
if (action === "run") {
|
||||
|
||||
@@ -11,10 +11,11 @@ export interface CommandResult {
|
||||
timedOut: boolean;
|
||||
}
|
||||
|
||||
export function runCommand(command: string[], cwd: string, options: { timeoutMs?: number } = {}): CommandResult {
|
||||
export function runCommand(command: string[], cwd: string, options: { timeoutMs?: number; env?: NodeJS.ProcessEnv } = {}): CommandResult {
|
||||
const result = spawnSync(command[0], command.slice(1), {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
env: options.env,
|
||||
maxBuffer: 1024 * 1024 * 8,
|
||||
timeout: options.timeoutMs,
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface UniDeskConfig {
|
||||
publicHost: string;
|
||||
core: { port: number; containerPort: number };
|
||||
frontend: { port: number; containerPort: number };
|
||||
devFrontend: { port: number; containerPort: number };
|
||||
database: { port: number; containerPort: number };
|
||||
providerIngress: { port: number; containerPort: number };
|
||||
restrictedHostAccess?: { bindHost: string; allowedSourceCidrs: string[] };
|
||||
@@ -247,6 +248,7 @@ export function readConfig(): UniDeskConfig {
|
||||
publicHost: stringField(network, "publicHost", "network"),
|
||||
core: portPair(network, "core"),
|
||||
frontend: portPair(network, "frontend"),
|
||||
devFrontend: portPair(network, "devFrontend"),
|
||||
database: portPair(network, "database"),
|
||||
providerIngress: portPair(network, "providerIngress"),
|
||||
restrictedHostAccess: optionalRestrictedHostAccess(network),
|
||||
|
||||
+26
-59
@@ -23,18 +23,9 @@ async function readJson(url: string, init?: RequestInit): Promise<unknown> {
|
||||
}
|
||||
|
||||
function coreInternalFetch(path: string, init?: { method?: string; body?: unknown }): unknown {
|
||||
const code = `
|
||||
const res = await fetch(${JSON.stringify(`http://backend-core:8080${path}`)}, ${JSON.stringify({
|
||||
method: init?.method ?? "GET",
|
||||
headers: init?.body === undefined ? undefined : { "content-type": "application/json" },
|
||||
body: init?.body === undefined ? undefined : JSON.stringify(init.body),
|
||||
})});
|
||||
const text = await res.text();
|
||||
let body = null;
|
||||
try { body = text ? JSON.parse(text) : null; } catch { body = { text }; }
|
||||
console.log(JSON.stringify({ ok: res.ok, status: res.status, body }));
|
||||
`;
|
||||
const result = runCommand(["docker", "exec", "unidesk-frontend", "bun", "-e", code], repoRoot);
|
||||
const command = ["docker", "exec", "unidesk-backend-core", "backend-core", "--fetch-json", `http://127.0.0.1:8080${path}`, "--method", init?.method ?? "GET"];
|
||||
if (init?.body !== undefined) command.push("--body-json", JSON.stringify(init.body));
|
||||
const result = runCommand(command, repoRoot);
|
||||
if (result.exitCode !== 0) {
|
||||
return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) };
|
||||
}
|
||||
@@ -46,13 +37,14 @@ function coreInternalFetch(path: string, init?: { method?: string; body?: unknow
|
||||
}
|
||||
|
||||
function coreDockerStatusSummary(): unknown {
|
||||
const code = `
|
||||
const res = await fetch('http://backend-core:8080/api/nodes/docker-status');
|
||||
const text = await res.text();
|
||||
let body = null;
|
||||
try { body = text ? JSON.parse(text) : null; } catch { body = { text }; }
|
||||
const dockerStatuses = (body?.dockerStatuses || []).map((item) => {
|
||||
const status = item.dockerStatus || {};
|
||||
const result = runCommand(["docker", "exec", "unidesk-backend-core", "backend-core", "--fetch-json", "http://127.0.0.1:8080/api/nodes/docker-status"], repoRoot);
|
||||
if (result.exitCode !== 0) {
|
||||
return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) };
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(result.stdout.trim()) as { ok?: boolean; status?: number; body?: { dockerStatuses?: Array<Record<string, unknown>> } };
|
||||
const dockerStatuses = (parsed.body?.dockerStatuses || []).map((item) => {
|
||||
const status = (item.dockerStatus || {}) as Record<string, unknown>;
|
||||
return {
|
||||
providerId: item.providerId,
|
||||
name: item.name,
|
||||
@@ -64,62 +56,37 @@ function coreDockerStatusSummary(): unknown {
|
||||
collectedAt: status.collectedAt,
|
||||
counts: status.counts,
|
||||
daemon: status.daemon,
|
||||
containersPreview: (status.containers || []).slice(0, 8).map((container) => ({
|
||||
id: container.id,
|
||||
name: container.name,
|
||||
image: container.image,
|
||||
state: container.state,
|
||||
status: container.status,
|
||||
ports: container.ports,
|
||||
})),
|
||||
containersPreview: Array.isArray(status.containers) ? status.containers.slice(0, 8) : [],
|
||||
},
|
||||
};
|
||||
});
|
||||
console.log(JSON.stringify({ ok: res.ok, status: res.status, body: { ok: body?.ok === true, dockerStatuses } }));
|
||||
`;
|
||||
const result = runCommand(["docker", "exec", "unidesk-frontend", "bun", "-e", code], repoRoot);
|
||||
if (result.exitCode !== 0) {
|
||||
return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) };
|
||||
}
|
||||
try {
|
||||
return JSON.parse(result.stdout.trim()) as unknown;
|
||||
return { ok: parsed.ok, status: parsed.status, body: { ok: parsed.body !== undefined, dockerStatuses } };
|
||||
} catch {
|
||||
return { ok: true, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) };
|
||||
}
|
||||
}
|
||||
|
||||
function coreSystemStatusSummary(): unknown {
|
||||
const code = `
|
||||
const res = await fetch('http://backend-core:8080/api/nodes/system-status?limit=24');
|
||||
const text = await res.text();
|
||||
let body = null;
|
||||
try { body = text ? JSON.parse(text) : null; } catch { body = { text }; }
|
||||
const systemStatuses = (body?.systemStatuses || []).map((item) => {
|
||||
const current = item.current || {};
|
||||
const result = runCommand(["docker", "exec", "unidesk-backend-core", "backend-core", "--fetch-json", "http://127.0.0.1:8080/api/nodes/system-status?limit=24"], repoRoot);
|
||||
if (result.exitCode !== 0) {
|
||||
return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) };
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(result.stdout.trim()) as { ok?: boolean; status?: number; body?: { systemStatuses?: Array<Record<string, unknown>> } };
|
||||
const systemStatuses = (parsed.body?.systemStatuses || []).map((item) => {
|
||||
const current = (item.current || {}) as Record<string, unknown>;
|
||||
const history = Array.isArray(item.history) ? item.history : [];
|
||||
return {
|
||||
providerId: item.providerId,
|
||||
name: item.name,
|
||||
nodeStatus: item.nodeStatus,
|
||||
updatedAt: item.updatedAt,
|
||||
current: item.current ? {
|
||||
ok: current.ok,
|
||||
collectedAt: current.collectedAt,
|
||||
cpu: current.cpu,
|
||||
memory: current.memory,
|
||||
disk: current.disk,
|
||||
} : null,
|
||||
historyPreview: (item.history || []).slice(-8),
|
||||
historyCount: (item.history || []).length,
|
||||
current: item.current ? { ok: current.ok, collectedAt: current.collectedAt, cpu: current.cpu, memory: current.memory, disk: current.disk } : null,
|
||||
historyPreview: history.slice(-8),
|
||||
historyCount: history.length,
|
||||
};
|
||||
});
|
||||
console.log(JSON.stringify({ ok: res.ok, status: res.status, body: { ok: body?.ok === true, systemStatuses } }));
|
||||
`;
|
||||
const result = runCommand(["docker", "exec", "unidesk-frontend", "bun", "-e", code], repoRoot);
|
||||
if (result.exitCode !== 0) {
|
||||
return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) };
|
||||
}
|
||||
try {
|
||||
return JSON.parse(result.stdout.trim()) as unknown;
|
||||
return { ok: parsed.ok, status: parsed.status, body: { ok: parsed.body !== undefined, systemStatuses } };
|
||||
} catch {
|
||||
return { ok: true, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) };
|
||||
}
|
||||
|
||||
@@ -131,8 +131,8 @@ const nativeK3sInstallVersion = "v1.34.1+k3s1";
|
||||
const nativeK3sImage = "rancher/k3s:v1.34.1-k3s1";
|
||||
const nativeK3sCtrAddress = "/run/k3s/containerd/containerd.sock";
|
||||
const unideskRepoUrl = "https://github.com/pikasTech/unidesk";
|
||||
const d601MaintenanceDeployAllowedServiceIds = new Set<string>();
|
||||
const devApplySupportedServiceIds = new Set<string>();
|
||||
const d601MaintenanceDeployAllowedServiceIds = new Set<string>(["backend-core", "frontend"]);
|
||||
const devApplySupportedServiceIds = new Set<string>(["backend-core", "frontend"]);
|
||||
const deployEnvironmentTargets: Record<DeployEnvironment, DeployEnvironmentTarget> = {
|
||||
dev: {
|
||||
environment: "dev",
|
||||
@@ -195,7 +195,7 @@ function deployHelp(action: string | undefined = undefined): Record<string, unkn
|
||||
},
|
||||
options: [
|
||||
{ name: "--file <path>", default: defaultDeployFile, description: "Desired-state manifest path relative to the repo root. JSON and ESM JS manifests are supported, for example deploy.json or develop.js." },
|
||||
{ name: "--env <dev|prod>", description: "Read the named environment from origin/master:deploy.json. Direct D601 service apply is disabled in the current CI-only phase." },
|
||||
{ name: "--env <dev|prod>", description: "Read the named environment from origin/master:deploy.json. Dev apply is enabled only for backend-core and frontend in D601 unidesk-dev." },
|
||||
{ name: "--service <id>", description: "Limit reconcile to one service from the manifest." },
|
||||
{ name: "--dry-run", description: "Prepare and validate without mutating the target service." },
|
||||
{ name: "--force", description: "Redeploy even when the live commit appears up to date." },
|
||||
@@ -1005,6 +1005,7 @@ function dockerBuildTimeoutMs(service: UniDeskMicroserviceConfig, options: Deplo
|
||||
|
||||
function devK3sPrepullImages(service: UniDeskMicroserviceConfig): string[] {
|
||||
if (!isDevK3sDeployService(service)) return [];
|
||||
if (service.id === "backend-core") return ["rust:1-bookworm", "postgres:16-bookworm"];
|
||||
return ["oven/bun:1-alpine"];
|
||||
}
|
||||
|
||||
@@ -2169,7 +2170,7 @@ async function applyOneService(config: UniDeskConfig, service: UniDeskMicroservi
|
||||
ok: false,
|
||||
serviceId: service.id,
|
||||
skipped: true,
|
||||
reason: `D601 maintenance-channel direct deployment is disabled for ${service.id}. Current dev automation is CI-only; use ci run-dev-e2e for the temporary namespace smoke runner.`,
|
||||
reason: `D601 dev deployment is allowed only for backend-core and frontend through the controlled deploy --env dev path; ${service.id} is not enabled. Use ci run-dev-e2e for smoke verification.`,
|
||||
steps,
|
||||
};
|
||||
}
|
||||
@@ -2344,7 +2345,7 @@ function blockedD601MaintenanceDeployServices(config: UniDeskConfig, manifest: D
|
||||
}
|
||||
|
||||
function d601MaintenanceDeployBlockMessage(blocked: string[]): string {
|
||||
return `D601 maintenance-channel direct deployment is disabled for direct/managed services in the current CI-only phase; blocked services: ${blocked.join(", ")}. Use ci run-dev-e2e for dev smoke verification.`;
|
||||
return `D601 dev deployment is enabled only for backend-core and frontend through deploy --env dev; blocked services: ${blocked.join(", ")}. Use ci run-dev-e2e for dev smoke verification.`;
|
||||
}
|
||||
|
||||
async function runApplyNow(config: UniDeskConfig, manifest: DeployManifest, options: DeployOptions): Promise<Record<string, unknown>> {
|
||||
@@ -2396,7 +2397,7 @@ export async function runDeployCommand(config: UniDeskConfig | null, args: strin
|
||||
if (options.environment !== "dev") throw new Error("deploy apply --env prod is not enabled yet");
|
||||
const unsupported = unsupportedDevApplyServices(manifest, options.serviceId);
|
||||
if (unsupported.length > 0) {
|
||||
throw new Error(`deploy apply --env dev is disabled for direct service rollout in the current CI-only phase; unsupported selected services: ${unsupported.join(", ")}. Use ci run-dev-e2e for dev smoke verification.`);
|
||||
throw new Error(`deploy apply --env dev currently supports only backend-core and frontend; unsupported selected services: ${unsupported.join(", ")}. Use ci run-dev-e2e for smoke verification.`);
|
||||
}
|
||||
if (config === null) throw new Error("deploy apply --env dev requires config.json");
|
||||
if (!options.dryRun) {
|
||||
|
||||
+19
-31
@@ -20,7 +20,7 @@ export interface ContainerStatus {
|
||||
ports: string;
|
||||
}
|
||||
|
||||
const rebuildableServices = ["backend-core", "frontend", "provider-gateway", "todo-note", "code-queue-mgr", "project-manager", "baidu-netdisk", "oa-event-flow"] as const;
|
||||
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];
|
||||
|
||||
export function isRebuildableService(value: string | undefined): value is RebuildableService {
|
||||
@@ -28,7 +28,6 @@ export function isRebuildableService(value: string | undefined): value is Rebuil
|
||||
}
|
||||
|
||||
export function resolveComposeCommand(config: UniDeskConfig, envFile: string): string[] {
|
||||
assertCanonicalServerRoot(config);
|
||||
const composeFile = rootPath(config.docker.composeFile);
|
||||
if (commandOk(["docker", "compose", "version"], repoRoot)) {
|
||||
return ["docker", "compose", "--env-file", envFile, "-f", composeFile, "-p", config.docker.projectName];
|
||||
@@ -54,21 +53,7 @@ function envValue(value: string): string {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function assertCanonicalServerRoot(config: UniDeskConfig): void {
|
||||
const canonicalRoot = resolve(config.providerGateway.upgrade.hostProjectRoot);
|
||||
const currentRoot = resolve(repoRoot);
|
||||
if (currentRoot !== canonicalRoot) {
|
||||
throw new Error([
|
||||
"Main-server Docker Compose commands must run from the canonical UniDesk root.",
|
||||
`canonicalRoot=${canonicalRoot}`,
|
||||
`currentRoot=${currentRoot}`,
|
||||
`Run from the canonical root: cd ${canonicalRoot} && bun scripts/cli.ts server <start|status|logs|rebuild|stop>`,
|
||||
].join(" "));
|
||||
}
|
||||
}
|
||||
|
||||
export function writeComposeEnv(config: UniDeskConfig, freshLogPrefix: boolean): ComposeRuntimeEnv {
|
||||
assertCanonicalServerRoot(config);
|
||||
const stateDir = rootPath(config.paths.stateDir);
|
||||
mkdirSync(stateDir, { recursive: true });
|
||||
const envFile = join(stateDir, "docker-compose.env");
|
||||
@@ -107,6 +92,7 @@ export function writeComposeEnv(config: UniDeskConfig, freshLogPrefix: boolean):
|
||||
UNIDESK_PUBLIC_HOST: config.network.publicHost,
|
||||
UNIDESK_CORE_PORT: String(config.network.core.port),
|
||||
UNIDESK_FRONTEND_PORT: String(config.network.frontend.port),
|
||||
UNIDESK_DEV_FRONTEND_PORT: String(config.network.devFrontend.port),
|
||||
UNIDESK_DATABASE_PORT: String(config.network.database.port),
|
||||
UNIDESK_DATABASE_BIND_HOST: runtimeSecretWithDefault("UNIDESK_DATABASE_BIND_HOST", restrictedHostBind, "127.0.0.1"),
|
||||
UNIDESK_PROVIDER_INGRESS_PORT: String(config.network.providerIngress.port),
|
||||
@@ -141,10 +127,6 @@ export function writeComposeEnv(config: UniDeskConfig, freshLogPrefix: boolean):
|
||||
UNIDESK_LOG_DAY: logDay,
|
||||
UNIDESK_LOG_PREFIX: logPrefix,
|
||||
UNIDESK_LOG_RETENTION_BYTES: runtimeSecret("UNIDESK_LOG_RETENTION_BYTES") || "1GiB",
|
||||
UNIDESK_FRONTEND_DEPLOY_SERVICE_ID: runtimeSecret("UNIDESK_FRONTEND_DEPLOY_SERVICE_ID") || "frontend",
|
||||
UNIDESK_FRONTEND_DEPLOY_REPO: runtimeSecret("UNIDESK_FRONTEND_DEPLOY_REPO"),
|
||||
UNIDESK_FRONTEND_DEPLOY_COMMIT: runtimeSecret("UNIDESK_FRONTEND_DEPLOY_COMMIT"),
|
||||
UNIDESK_FRONTEND_DEPLOY_REQUESTED_COMMIT: runtimeSecret("UNIDESK_FRONTEND_DEPLOY_REQUESTED_COMMIT"),
|
||||
UNIDESK_HOST_ROOT_SSH_DIR: process.env.UNIDESK_HOST_ROOT_SSH_DIR || "/root/.ssh",
|
||||
UNIDESK_HOST_SSH_KEY_DIR: config.sshForwarding.keyDir,
|
||||
UNIDESK_HOST_SSH_HOST: config.sshForwarding.host,
|
||||
@@ -159,13 +141,16 @@ export function writeComposeEnv(config: UniDeskConfig, freshLogPrefix: boolean):
|
||||
UNIDESK_TODO_NOTE_REMINDER_SCAN_INTERVAL_MS: runtimeSecret("UNIDESK_TODO_NOTE_REMINDER_SCAN_INTERVAL_MS") || "30000",
|
||||
UNIDESK_TODO_NOTE_REMINDER_CLAUDEQQ_TIMEOUT_MS: runtimeSecret("UNIDESK_TODO_NOTE_REMINDER_CLAUDEQQ_TIMEOUT_MS") || "15000",
|
||||
UNIDESK_TODO_NOTE_REMINDER_CLAUDEQQ_SEND_ATTEMPTS: runtimeSecret("UNIDESK_TODO_NOTE_REMINDER_CLAUDEQQ_SEND_ATTEMPTS") || "3",
|
||||
UNIDESK_CODE_QUEUE_MGR_DATABASE_POOL_MAX: runtimeSecret("UNIDESK_CODE_QUEUE_MGR_DATABASE_POOL_MAX") || "2",
|
||||
UNIDESK_CODE_QUEUE_TRACE_DATABASE_POOL_MAX: runtimeSecret("UNIDESK_CODE_QUEUE_TRACE_DATABASE_POOL_MAX") || "1",
|
||||
UNIDESK_CODE_QUEUE_MINIMAX_API_KEY: runtimeSecret("UNIDESK_CODE_QUEUE_MINIMAX_API_KEY") || runtimeSecret("MINIMAX_API_KEY"),
|
||||
UNIDESK_CODE_QUEUE_MINIMAX_MODEL: runtimeSecret("UNIDESK_CODE_QUEUE_MINIMAX_MODEL") || runtimeSecret("MINIMAX_MODEL") || "MiniMax-M2.7",
|
||||
UNIDESK_CODE_QUEUE_MINIMAX_API_BASE: runtimeSecret("UNIDESK_CODE_QUEUE_MINIMAX_API_BASE") || runtimeSecret("MINIMAX_API_BASE") || "https://api.minimaxi.com/v1",
|
||||
UNIDESK_CODE_QUEUE_MINIMAX_JUDGE_TIMEOUT_MS: runtimeSecretWithDefault("UNIDESK_CODE_QUEUE_MINIMAX_JUDGE_TIMEOUT_MS", "90000", "60000"),
|
||||
UNIDESK_CODE_QUEUE_REMOTE_WORKDIR: runtimeSecret("UNIDESK_CODE_QUEUE_REMOTE_WORKDIR") || "/home/ubuntu",
|
||||
UNIDESK_CODE_QUEUE_MAIN_PROVIDER_ID: runtimeSecret("UNIDESK_CODE_QUEUE_MAIN_PROVIDER_ID") || "D601",
|
||||
UNIDESK_CODE_QUEUE_TRACE_DATABASE_URL: runtimeSecret("UNIDESK_CODE_QUEUE_TRACE_DATABASE_URL")
|
||||
|| `postgres://${config.database.user}:${config.database.password}@database:${config.network.database.containerPort}/${config.database.name}`,
|
||||
UNIDESK_CODE_QUEUE_MGR_DATABASE_POOL_MAX: runtimeSecret("UNIDESK_CODE_QUEUE_MGR_DATABASE_POOL_MAX") || "2",
|
||||
UNIDESK_CODE_QUEUE_TRACE_DATABASE_POOL_MAX: runtimeSecret("UNIDESK_CODE_QUEUE_TRACE_DATABASE_POOL_MAX") || "1",
|
||||
UNIDESK_CODE_QUEUE_EXECUTION_PROVIDER_IDS: runtimeSecret("UNIDESK_CODE_QUEUE_EXECUTION_PROVIDER_IDS") || "D601",
|
||||
UNIDESK_CODE_QUEUE_DEV_CONTAINER_DEFAULT_PROVIDER_ID: runtimeSecret("UNIDESK_CODE_QUEUE_DEV_CONTAINER_DEFAULT_PROVIDER_ID") || "D601",
|
||||
UNIDESK_CODE_QUEUE_DEV_CONTAINER_IMAGE: runtimeSecret("UNIDESK_CODE_QUEUE_DEV_CONTAINER_IMAGE"),
|
||||
@@ -250,10 +235,11 @@ export function rebuildService(config: UniDeskConfig, service: RebuildableServic
|
||||
const watchdogLog = rootPath(".state", "jobs", "compose-rebuild-watchdog.log");
|
||||
const watchdogInnerScript = [
|
||||
"set -euo pipefail",
|
||||
"sleep 20",
|
||||
`cid=$(${shellJoin(listServiceContainersCommand)} || true)`,
|
||||
`if [ -z "$cid" ]; then echo "$(date -Is) compose_rebuild_watchdog_restore service=${service}" >> ${shellQuote(watchdogLog)}; ${shellJoin(restoreCommand)} >> ${shellQuote(watchdogLog)} 2>&1 || true; fi`,
|
||||
].join("\n");
|
||||
const watchdogScript = `set -euo pipefail; sleep 20; ${shellJoin(["flock", "-w", "300", lockPath, "bash", "-lc", watchdogInnerScript])} || true`;
|
||||
const watchdogScript = `set -euo pipefail; ${shellJoin(["flock", "-w", "300", lockPath, "bash", "-lc", watchdogInnerScript])} || true`;
|
||||
const validateScript = [
|
||||
"ready=0",
|
||||
"for attempt in $(seq 1 60); do",
|
||||
@@ -308,6 +294,7 @@ export function rebuildService(config: UniDeskConfig, service: RebuildableServic
|
||||
function fixedPorts(config: UniDeskConfig): Array<{ name: string; port: number; listening: boolean }> {
|
||||
return [
|
||||
{ name: "frontend", port: config.network.frontend.port, listening: isPortListening(config.network.frontend.port) },
|
||||
{ name: "dev-frontend", port: config.network.devFrontend.port, listening: isPortListening(config.network.devFrontend.port) },
|
||||
{ name: "provider-ingress", port: config.network.providerIngress.port, listening: isPortListening(config.network.providerIngress.port) },
|
||||
];
|
||||
}
|
||||
@@ -356,9 +343,7 @@ function composeLockedScript(innerScript: string): string {
|
||||
"set -euo pipefail",
|
||||
`mkdir -p ${shellQuote(rootPath(".state", "locks"))}`,
|
||||
`echo ${shellJoin(["compose_lock_wait", lockPath])}`,
|
||||
// Prevent background helpers spawned by the locked script from inheriting
|
||||
// the compose lock fd and blocking later server rebuild jobs.
|
||||
shellJoin(["flock", "-o", lockPath, "bash", "-lc", innerScript]),
|
||||
shellJoin(["flock", lockPath, "bash", "-lc", innerScript]),
|
||||
].join("; ");
|
||||
}
|
||||
|
||||
@@ -409,8 +394,8 @@ async function probe(url: string): Promise<unknown> {
|
||||
}
|
||||
}
|
||||
|
||||
function dockerExecJson(container: string, code: string): unknown {
|
||||
const result = runCommand(["docker", "exec", container, "bun", "-e", code], repoRoot);
|
||||
function dockerExecJson(container: string, path: string): unknown {
|
||||
const result = runCommand(["docker", "exec", container, "backend-core", "--fetch-json", `http://127.0.0.1:8080${path}`], repoRoot);
|
||||
if (result.exitCode !== 0) {
|
||||
return { ok: false, exitCode: result.exitCode, stdout: result.stdout.slice(-1200), stderr: result.stderr.slice(-1200) };
|
||||
}
|
||||
@@ -433,8 +418,8 @@ export async function stackStatus(config: UniDeskConfig): Promise<unknown> {
|
||||
const databaseBindHost = runtimeValue("UNIDESK_DATABASE_BIND_HOST") || "127.0.0.1";
|
||||
const oaEventFlowBindHost = runtimeValue("UNIDESK_OA_EVENT_FLOW_BIND_HOST") || "127.0.0.1";
|
||||
const oaEventFlowPort = Number(runtimeValue("UNIDESK_OA_EVENT_FLOW_PORT") || "4255");
|
||||
const coreHealth = dockerExecJson("unidesk-backend-core", "fetch('http://127.0.0.1:8080/health').then(r=>r.json()).then(j=>console.log(JSON.stringify({ok:true,status:200,body:j}))).catch(e=>{console.log(JSON.stringify({ok:false,error:String(e)}));process.exit(1)})");
|
||||
const overview = dockerExecJson("unidesk-backend-core", "fetch('http://127.0.0.1:8080/api/overview').then(r=>r.json()).then(j=>console.log(JSON.stringify({ok:true,status:200,body:j}))).catch(e=>{console.log(JSON.stringify({ok:false,error:String(e)}));process.exit(1)})");
|
||||
const coreHealth = dockerExecJson("unidesk-backend-core", "/health");
|
||||
const overview = dockerExecJson("unidesk-backend-core", "/api/overview");
|
||||
return {
|
||||
runtimeEnv,
|
||||
swap: swapStatus(),
|
||||
@@ -462,6 +447,7 @@ export async function stackStatus(config: UniDeskConfig): Promise<unknown> {
|
||||
],
|
||||
internalPorts: [
|
||||
{ name: "backend-core", containerPort: config.network.core.containerPort, hostPort: null },
|
||||
{ name: "dev-frontend-proxy", containerPort: config.network.devFrontend.containerPort, hostPort: config.network.devFrontend.port },
|
||||
{ name: "database", containerPort: config.network.database.containerPort, hostPort: null },
|
||||
{ name: "project-manager", containerPort: 4233, hostPort: null },
|
||||
{ name: "baidu-netdisk", containerPort: 4244, hostPort: null },
|
||||
@@ -471,12 +457,14 @@ export async function stackStatus(config: UniDeskConfig): Promise<unknown> {
|
||||
health: {
|
||||
core: coreHealth,
|
||||
frontend: await probe(`http://127.0.0.1:${config.network.frontend.port}/health`),
|
||||
devFrontend: await probe(`http://127.0.0.1:${config.network.devFrontend.port}/health`),
|
||||
providerIngress: await probe(`http://127.0.0.1:${config.network.providerIngress.port}/health`),
|
||||
database: dockerExec(config, "unidesk-database", ["pg_isready", "-U", config.database.user, "-d", config.database.name]),
|
||||
overview,
|
||||
},
|
||||
urls: {
|
||||
frontend: `http://${config.network.publicHost}:${config.network.frontend.port}`,
|
||||
devFrontend: `http://${config.network.publicHost}:${config.network.devFrontend.port}`,
|
||||
providerIngress: `ws://${config.network.publicHost}:${config.network.providerIngress.port}/ws/provider`,
|
||||
internalCore: `http://backend-core:${config.network.core.containerPort}`,
|
||||
internalDatabase: `postgres://${config.database.user}:***@database:${config.network.database.containerPort}/${config.database.name}`,
|
||||
@@ -507,7 +495,7 @@ export function stackLogs(config: UniDeskConfig, tailBytes: number): unknown {
|
||||
const truncated = sizeBytes > tailBytes;
|
||||
return { path, name: basename(path), sizeBytes, tailBytes, truncated, tail: tailFile(path, tailBytes) };
|
||||
});
|
||||
const containerNames = ["unidesk-database", "unidesk-backend-core", "unidesk-frontend", "unidesk-provider-gateway-main", "todo-note-backend", "project-manager-backend", "baidu-netdisk-backend", "oa-event-flow-backend"];
|
||||
const containerNames = ["unidesk-database", "unidesk-backend-core", "unidesk-frontend", "unidesk-dev-frontend-proxy", "unidesk-provider-gateway-main", "todo-note-backend", "project-manager-backend", "baidu-netdisk-backend", "oa-event-flow-backend"];
|
||||
const docker = containerNames.map((name) => {
|
||||
const result = runCommand(["docker", "logs", "--tail", "40", name], repoRoot);
|
||||
return {
|
||||
|
||||
+5
-15
@@ -769,18 +769,9 @@ function runPsql(config: UniDeskConfig, sql: string): { ok: boolean; stdout: str
|
||||
}
|
||||
|
||||
function dockerCoreJson(path: string, init?: { method?: string; body?: unknown }): unknown {
|
||||
const code = `
|
||||
const res = await fetch(${JSON.stringify(`http://127.0.0.1:8080${path}`)}, ${JSON.stringify({
|
||||
method: init?.method ?? "GET",
|
||||
headers: init?.body === undefined ? undefined : { "content-type": "application/json" },
|
||||
body: init?.body === undefined ? undefined : JSON.stringify(init.body),
|
||||
})});
|
||||
const text = await res.text();
|
||||
let body = null;
|
||||
try { body = text ? JSON.parse(text) : null; } catch { body = { text }; }
|
||||
console.log(JSON.stringify({ ok: res.ok, status: res.status, body }));
|
||||
`;
|
||||
const result = runCommand(["docker", "exec", "unidesk-backend-core", "bun", "-e", code], repoRoot);
|
||||
const command = ["docker", "exec", "unidesk-backend-core", "backend-core", "--fetch-json", `http://127.0.0.1:8080${path}`, "--method", init?.method ?? "GET"];
|
||||
if (init?.body !== undefined) command.push("--body-json", JSON.stringify(init.body));
|
||||
const result = runCommand(command, repoRoot);
|
||||
if (result.exitCode !== 0) return { ok: false, exitCode: result.exitCode, stdout: result.stdout.slice(-1200), stderr: result.stderr.slice(-1200) };
|
||||
try {
|
||||
return JSON.parse(result.stdout.trim()) as unknown;
|
||||
@@ -1872,10 +1863,9 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
|
||||
await page.waitForSelector('[data-testid="decision-center-record-table"]', { timeout: 30000 });
|
||||
await page.waitForFunction((title) => {
|
||||
const text = document.body.innerText;
|
||||
const lowerText = text.toLowerCase();
|
||||
return text.includes("Decision Center")
|
||||
&& text.includes("G0/G1 目标")
|
||||
&& lowerText.includes("p0/p1 blocker")
|
||||
&& text.includes("P0/P1 Blocker")
|
||||
&& text.includes("停放事项")
|
||||
&& text.includes("最近会议/决议")
|
||||
&& text.includes("查看原始JSON")
|
||||
@@ -2942,7 +2932,7 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
|
||||
addSelectedCheck(checks, options, "frontend:decision-center-visible",
|
||||
decisionCenterTextLower.includes("decision center")
|
||||
&& decisionCenterText.includes("G0/G1 目标")
|
||||
&& decisionCenterTextLower.includes("p0/p1 blocker")
|
||||
&& decisionCenterText.includes("P0/P1 Blocker")
|
||||
&& decisionCenterText.includes("停放事项")
|
||||
&& decisionCenterText.includes("最近会议/决议")
|
||||
&& decisionCenterText.includes("全部记录")
|
||||
|
||||
@@ -6,50 +6,9 @@ import { jsonByteLength, previewJson } from "./preview";
|
||||
export function coreInternalFetch(path: string, init?: { method?: string; body?: unknown; maxResponseBytes?: number }): unknown {
|
||||
if (!path.startsWith("/")) throw new Error("core internal path must start with /");
|
||||
const maxResponseBytes = Math.max(1024, Math.floor(init?.maxResponseBytes ?? 5_000_000));
|
||||
const code = `
|
||||
const res = await fetch(${JSON.stringify(`http://backend-core:8080${path}`)}, ${JSON.stringify({
|
||||
method: init?.method ?? "GET",
|
||||
headers: init?.body === undefined ? undefined : { "content-type": "application/json" },
|
||||
body: init?.body === undefined ? undefined : JSON.stringify(init.body),
|
||||
})});
|
||||
const maxResponseBytes = ${JSON.stringify(maxResponseBytes)};
|
||||
const reader = res.body?.getReader();
|
||||
const chunks = [];
|
||||
let bytes = 0;
|
||||
let responseTruncated = false;
|
||||
if (reader) {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
if (bytes + value.byteLength > maxResponseBytes) {
|
||||
const keep = Math.max(0, maxResponseBytes - bytes);
|
||||
if (keep > 0) {
|
||||
chunks.push(value.slice(0, keep));
|
||||
bytes += keep;
|
||||
}
|
||||
responseTruncated = true;
|
||||
try { await reader.cancel(); } catch {}
|
||||
break;
|
||||
}
|
||||
chunks.push(value);
|
||||
bytes += value.byteLength;
|
||||
}
|
||||
}
|
||||
const buffer = new Uint8Array(bytes);
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
buffer.set(chunk, offset);
|
||||
offset += chunk.byteLength;
|
||||
}
|
||||
const text = new TextDecoder().decode(buffer);
|
||||
let body = null;
|
||||
try { body = text && !responseTruncated ? JSON.parse(text) : null; } catch { body = { text }; }
|
||||
if (responseTruncated) {
|
||||
body = { _unideskResponseTruncated: true, maxResponseBytes, bytesRead: bytes, contentLength: res.headers.get("content-length"), textPreview: text };
|
||||
}
|
||||
console.log(JSON.stringify({ ok: res.ok, status: res.status, responseTruncated, responseBytesRead: bytes, responseContentLength: res.headers.get("content-length"), body }));
|
||||
`;
|
||||
const result = runCommand(["docker", "exec", "unidesk-frontend", "bun", "-e", code], repoRoot);
|
||||
const command = ["docker", "exec", "unidesk-backend-core", "backend-core", "--fetch-json", `http://127.0.0.1:8080${path}`, "--method", init?.method ?? "GET", "--max-response-bytes", String(maxResponseBytes)];
|
||||
if (init?.body !== undefined) command.push("--body-json", JSON.stringify(init.body));
|
||||
const result = runCommand(command, repoRoot);
|
||||
if (result.exitCode !== 0) {
|
||||
return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) };
|
||||
}
|
||||
|
||||
+3
-7
@@ -805,13 +805,9 @@ export async function runSsh(config: UniDeskConfig, providerId: string, args: st
|
||||
const child = spawn("docker", [
|
||||
"exec",
|
||||
"-i",
|
||||
"-e",
|
||||
`PROVIDER_TOKEN=${config.providerGateway.token}`,
|
||||
"unidesk-frontend",
|
||||
"bun",
|
||||
"-e",
|
||||
brokerSource(),
|
||||
"--",
|
||||
"unidesk-backend-core",
|
||||
"backend-core",
|
||||
"--ssh-broker",
|
||||
JSON.stringify(payload),
|
||||
], {
|
||||
cwd: repoRoot,
|
||||
|
||||
Reference in New Issue
Block a user