feat: add code queue services and baidu netdisk

This commit is contained in:
Codex
2026-05-13 00:59:36 +00:00
parent ae462ed9ef
commit 6a04144d3f
63 changed files with 10983 additions and 1101 deletions
+45 -25
View File
@@ -1,5 +1,5 @@
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import { basename, join, resolve } from "node:path";
import { basename, dirname, join, resolve } from "node:path";
import { commandOk, runCommand, tailFile } from "./command";
import { type UniDeskConfig, repoRoot, rootPath } from "./config";
import { startJob } from "./jobs";
@@ -7,6 +7,7 @@ import { startJob } from "./jobs";
export interface ComposeRuntimeEnv {
envFile: string;
logDir: string;
logDay: string;
logPrefix: string;
}
@@ -18,7 +19,7 @@ export interface ContainerStatus {
ports: string;
}
const rebuildableServices = ["backend-core", "frontend", "provider-gateway", "todo-note", "codex-queue", "project-manager"] as const;
const rebuildableServices = ["backend-core", "frontend", "provider-gateway", "todo-note", "code-queue", "project-manager", "baidu-netdisk"] as const;
export type RebuildableService = typeof rebuildableServices[number];
export function isRebuildableService(value: string | undefined): value is RebuildableService {
@@ -58,18 +59,25 @@ export function writeComposeEnv(config: UniDeskConfig, freshLogPrefix: boolean):
const previousRaw = existsSync(envFile) ? readFileSync(envFile, "utf8") : "";
const previousValue = (key: string): string => previousRaw.match(new RegExp(`^${key}=(.*)$`, "m"))?.[1]?.replace(/^"|"$/g, "") ?? "";
const runtimeSecret = (key: string): string => process.env[key] ?? previousValue(key);
let logDir: string;
let logRoot: string;
let logDay: string;
let logPrefix: string;
if (!freshLogPrefix && previousRaw.length > 0) {
logDir = previousValue("UNIDESK_LOG_DIR") || rootPath(config.paths.logsDir);
const previousLogDir = previousValue("UNIDESK_LOG_DIR");
const previousLogRoot = previousLogDir && /^\d{8}$/u.test(basename(previousLogDir)) ? dirname(previousLogDir) : previousLogDir;
logRoot = previousLogRoot || rootPath(config.paths.logsDir);
logPrefix = previousValue("UNIDESK_LOG_PREFIX") || localDateParts(new Date()).stamp;
logDay = previousValue("UNIDESK_LOG_DAY") || logPrefix.match(/^\d{8}/u)?.[0] || localDateParts(new Date()).day;
} else {
const parts = localDateParts(new Date());
logDir = resolve(rootPath(config.paths.logsDir, parts.day));
logRoot = resolve(rootPath(config.paths.logsDir));
logDay = parts.day;
logPrefix = parts.stamp;
}
mkdirSync(logDir, { recursive: true });
chmodSync(logDir, 0o777);
logRoot = resolve(logRoot);
mkdirSync(join(logRoot, logDay), { recursive: true });
chmodSync(logRoot, 0o777);
chmodSync(join(logRoot, logDay), 0o777);
const labels = JSON.stringify(config.providerGateway.labels);
const microservices = JSON.stringify(config.microservices);
const lines = {
@@ -105,8 +113,10 @@ export function writeComposeEnv(config: UniDeskConfig, freshLogPrefix: boolean):
UNIDESK_PROVIDER_UPGRADE_COMPOSE_PROJECT: config.providerGateway.upgrade.composeProject,
UNIDESK_PROVIDER_UPGRADE_SERVICE: config.providerGateway.upgrade.service,
UNIDESK_PROVIDER_UPGRADE_RUNNER_IMAGE: config.providerGateway.upgrade.runnerImage,
UNIDESK_LOG_DIR: logDir,
UNIDESK_LOG_DIR: logRoot,
UNIDESK_LOG_DAY: logDay,
UNIDESK_LOG_PREFIX: logPrefix,
UNIDESK_LOG_RETENTION_BYTES: runtimeSecret("UNIDESK_LOG_RETENTION_BYTES") || "1GiB",
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,
@@ -121,23 +131,32 @@ 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_CODEX_QUEUE_MINIMAX_API_KEY: runtimeSecret("UNIDESK_CODEX_QUEUE_MINIMAX_API_KEY"),
UNIDESK_CODEX_QUEUE_MINIMAX_MODEL: runtimeSecret("UNIDESK_CODEX_QUEUE_MINIMAX_MODEL") || "MiniMax-M2.7",
UNIDESK_CODEX_QUEUE_MINIMAX_API_BASE: runtimeSecret("UNIDESK_CODEX_QUEUE_MINIMAX_API_BASE") || "https://api.minimaxi.com/v1",
UNIDESK_CODEX_QUEUE_MINIMAX_JUDGE_TIMEOUT_MS: runtimeSecret("UNIDESK_CODEX_QUEUE_MINIMAX_JUDGE_TIMEOUT_MS") || "60000",
UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_ENABLED: runtimeSecret("UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_ENABLED") || "true",
UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_BASE_URL: runtimeSecret("UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_BASE_URL") || "http://backend-core:8080/api/microservices/claudeqq/proxy",
UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_TARGET_TYPE: runtimeSecret("UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_TARGET_TYPE") || "private",
UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_USER_ID: runtimeSecret("UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_USER_ID") || "645275593",
UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_GROUP_ID: runtimeSecret("UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_GROUP_ID"),
UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_MAX_RESPONSE_CHARS: runtimeSecret("UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_MAX_RESPONSE_CHARS") || "12000",
UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_TIMEOUT_MS: runtimeSecret("UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_TIMEOUT_MS") || "15000",
UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_SEND_ATTEMPTS: runtimeSecret("UNIDESK_CODEX_QUEUE_NOTIFY_CLAUDEQQ_SEND_ATTEMPTS") || "3",
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: runtimeSecret("UNIDESK_CODE_QUEUE_MINIMAX_JUDGE_TIMEOUT_MS") || "60000",
UNIDESK_CODE_QUEUE_REMOTE_WORKDIR: runtimeSecret("UNIDESK_CODE_QUEUE_REMOTE_WORKDIR") || "/home/ubuntu",
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"),
UNIDESK_CODE_QUEUE_DEV_CONTAINER_WORKDIR: runtimeSecret("UNIDESK_CODE_QUEUE_DEV_CONTAINER_WORKDIR") || "/home/ubuntu",
UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_ENABLED: runtimeSecret("UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_ENABLED") || "true",
UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_BASE_URL: runtimeSecret("UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_BASE_URL") || "http://backend-core:8080/api/microservices/claudeqq/proxy",
UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_TARGET_TYPE: runtimeSecret("UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_TARGET_TYPE") || "private",
UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_USER_ID: runtimeSecret("UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_USER_ID") || "645275593",
UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_GROUP_ID: runtimeSecret("UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_GROUP_ID"),
UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_MAX_RESPONSE_CHARS: runtimeSecret("UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_MAX_RESPONSE_CHARS") || "12000",
UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_TIMEOUT_MS: runtimeSecret("UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_TIMEOUT_MS") || "15000",
UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_SEND_ATTEMPTS: runtimeSecret("UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_SEND_ATTEMPTS") || "3",
UNIDESK_BAIDU_NETDISK_CLIENT_ID: runtimeSecret("UNIDESK_BAIDU_NETDISK_CLIENT_ID"),
UNIDESK_BAIDU_NETDISK_CLIENT_SECRET: runtimeSecret("UNIDESK_BAIDU_NETDISK_CLIENT_SECRET"),
UNIDESK_BAIDU_NETDISK_TOKEN_KEY: runtimeSecret("UNIDESK_BAIDU_NETDISK_TOKEN_KEY"),
UNIDESK_BAIDU_NETDISK_APP_ROOT: runtimeSecret("UNIDESK_BAIDU_NETDISK_APP_ROOT") || "/apps/UniDeskBaiduNetdisk",
OPENAI_API_KEY: runtimeSecret("OPENAI_API_KEY"),
CRS_OAI_KEY: runtimeSecret("CRS_OAI_KEY"),
};
writeFileSync(envFile, Object.entries(lines).map(([key, value]) => `${key}=${envValue(value)}`).join("\n") + "\n", "utf8");
return { envFile, logDir, logPrefix };
return { envFile, logDir: logRoot, logDay, logPrefix };
}
export function composeConfig(config: UniDeskConfig): { runtimeEnv: ComposeRuntimeEnv; command: string[]; result: ReturnType<typeof runCommand> } {
@@ -226,7 +245,7 @@ export function rebuildService(config: UniDeskConfig, service: RebuildableServic
validateScript,
].join("\n");
const command = ["bash", "-lc", composeLockedScript(script)];
const jobRunner = service === "codex-queue" ? { runner: "docker" as const, dockerImage: "unidesk-codex-queue:latest" } : {};
const jobRunner = service === "code-queue" ? { runner: "docker" as const, dockerImage: "unidesk-code-queue:latest" } : {};
const job = startJob("server_rebuild", command, `Rebuild and validate UniDesk ${service} with serialized Docker Compose mutation`, jobRunner);
return {
job,
@@ -242,7 +261,7 @@ export function rebuildService(config: UniDeskConfig, service: RebuildableServic
noDeps: true,
forceRecreate: true,
composeMutationLock: rootPath(".state", "locks", "server-compose.lock"),
jobRunner: service === "codex-queue" ? "docker" : "local",
jobRunner: service === "code-queue" ? "docker" : "local",
postUpValidation: true,
namedVolumesPreserved: true,
},
@@ -352,8 +371,9 @@ export async function stackStatus(config: UniDeskConfig): Promise<unknown> {
internalPorts: [
{ name: "backend-core", containerPort: config.network.core.containerPort, hostPort: null },
{ name: "database", containerPort: config.network.database.containerPort, hostPort: null },
{ name: "codex-queue", containerPort: 4222, hostPort: null },
{ name: "code-queue", containerPort: 4222, hostPort: null },
{ name: "project-manager", containerPort: 4233, hostPort: null },
{ name: "baidu-netdisk", containerPort: 4244, hostPort: null },
],
containers: dockerContainers(config),
health: {
@@ -391,7 +411,7 @@ export function stackLogs(config: UniDeskConfig, tailBytes: number): unknown {
const currentFiles = allFiles.filter((path) => basename(path).startsWith(runtimeEnv.logPrefix));
const selectedFiles = (currentFiles.length > 0 ? currentFiles : allFiles.slice(-12)).slice(-12);
const files = selectedFiles.map((path) => ({ path, name: basename(path), tail: tailFile(path, tailBytes) }));
const containerNames = ["unidesk-database", "unidesk-backend-core", "unidesk-frontend", "unidesk-provider-gateway-main", "todo-note-backend", "codex-queue-backend", "project-manager-backend"];
const containerNames = ["unidesk-database", "unidesk-backend-core", "unidesk-frontend", "unidesk-provider-gateway-main", "todo-note-backend", "code-queue-backend", "project-manager-backend", "baidu-netdisk-backend"];
const docker = containerNames.map((name) => {
const result = runCommand(["docker", "logs", "--tail", "40", name], repoRoot);
return { name, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-tailBytes), stderrTail: result.stderr.slice(-tailBytes) };