Migrate Code Queue infra target to G14
This commit is contained in:
+57
-8
@@ -610,16 +610,65 @@
|
||||
"integrated": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "k3sctl-adapter-g14",
|
||||
"name": "k3s Control Plane G14",
|
||||
"providerId": "G14",
|
||||
"description": "k3sctl-adapter-g14 是 UniDesk 直管的 G14 k3s 控制平面适配微服务,专用于承载迁移后的 Code Queue 执行面和基础设施 CI/CD,不接管仍保留在 D601 的用户服务。",
|
||||
"repository": {
|
||||
"url": "https://github.com/pikasTech/unidesk",
|
||||
"commitId": "local",
|
||||
"dockerfile": "src/components/microservices/k3sctl-adapter/Dockerfile",
|
||||
"composeFile": "src/components/microservices/k3sctl-adapter/docker-compose.g14.yml",
|
||||
"composeService": "k3sctl-adapter-g14",
|
||||
"containerName": "k3sctl-adapter-g14"
|
||||
},
|
||||
"backend": {
|
||||
"nodeBaseUrl": "http://host.docker.internal:4266",
|
||||
"nodeBindHost": "127.0.0.1",
|
||||
"nodePort": 4266,
|
||||
"proxyMode": "provider-gateway-http",
|
||||
"frontendOnly": true,
|
||||
"public": false,
|
||||
"allowedMethods": [
|
||||
"GET",
|
||||
"HEAD",
|
||||
"POST",
|
||||
"DELETE",
|
||||
"PUT",
|
||||
"PATCH"
|
||||
],
|
||||
"allowedPathPrefixes": [
|
||||
"/health",
|
||||
"/logs",
|
||||
"/api/"
|
||||
],
|
||||
"healthPath": "/health",
|
||||
"timeoutMs": 30000
|
||||
},
|
||||
"deployment": {
|
||||
"mode": "unidesk-direct"
|
||||
},
|
||||
"development": {
|
||||
"providerId": "G14",
|
||||
"sshPassthrough": true,
|
||||
"worktreePath": "/root/unidesk"
|
||||
},
|
||||
"frontend": {
|
||||
"route": "/apps/k3sctl-g14",
|
||||
"integrated": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "code-queue",
|
||||
"name": "Code Queue",
|
||||
"providerId": "D601",
|
||||
"description": "Code Queue 的用户服务 ID 保持稳定;队列管理、提交、历史和轻量 Trace 读取默认由主 server code-queue-mgr 直管 PostgreSQL,D601 k3s Code Queue 只负责 scheduler/runner、active run steer/interrupt 和执行态写回。",
|
||||
"providerId": "G14",
|
||||
"description": "Code Queue 的用户服务 ID 保持稳定;队列管理、提交、历史和轻量 Trace 读取默认由主 server code-queue-mgr 直管 PostgreSQL,G14 k3s Code Queue 负责 scheduler/runner、active run steer/interrupt 和执行态写回。D601 仍保留未迁移用户服务的 k3s adapter。",
|
||||
"repository": {
|
||||
"url": "https://github.com/pikasTech/unidesk",
|
||||
"commitId": "2a9f60d57401bf9d6165e44af30c2f21ada79320",
|
||||
"dockerfile": "src/components/microservices/code-queue/Dockerfile",
|
||||
"composeFile": "src/components/microservices/k3sctl-adapter/k3s/code-queue.k3s.json",
|
||||
"composeFile": "src/components/microservices/k3sctl-adapter/k3s/code-queue.g14.k3s.json",
|
||||
"composeService": "code-queue",
|
||||
"containerName": "k3s:code-queue"
|
||||
},
|
||||
@@ -647,9 +696,9 @@
|
||||
"timeoutMs": 30000
|
||||
},
|
||||
"development": {
|
||||
"providerId": "D601",
|
||||
"providerId": "G14",
|
||||
"sshPassthrough": true,
|
||||
"worktreePath": "/home/ubuntu/unidesk-code-queue-deploy"
|
||||
"worktreePath": "/root/unidesk"
|
||||
},
|
||||
"frontend": {
|
||||
"route": "/apps/code-queue",
|
||||
@@ -657,13 +706,13 @@
|
||||
},
|
||||
"deployment": {
|
||||
"mode": "k3sctl-managed",
|
||||
"adapterServiceId": "k3sctl-adapter",
|
||||
"adapterServiceId": "k3sctl-adapter-g14",
|
||||
"k3sServiceId": "code-queue",
|
||||
"namespace": "unidesk",
|
||||
"expectedNodeIds": [
|
||||
"D601"
|
||||
"G14"
|
||||
],
|
||||
"activeNodeId": "D601"
|
||||
"activeNodeId": "G14"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+11
-6
@@ -115,11 +115,11 @@ export function checkHelp(): Record<string, unknown> {
|
||||
{ name: "--compose", description: "Render Docker Compose config." },
|
||||
{ name: "--logs", description: "Check unified log rotation policy." },
|
||||
{ name: "--recovery-guardrails", description: "Run D601 k3s/Code Queue reboot recovery diagnostics in read-only mode." },
|
||||
{ name: "--rust", description: "Run cargo check only when UNIDESK_D601_RUST_CHECK=1 is set inside D601 CI/dev execution." },
|
||||
{ name: "--rust", description: "Run cargo check only when UNIDESK_D601_RUST_CHECK=1 or UNIDESK_NATIVE_K3S_RUST_CHECK=1 is set inside an approved native k3s 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",
|
||||
nativeK3sCi: "use deploy apply --env dev --service backend-core and CI with UNIDESK_NATIVE_K3S_RUST_CHECK=1",
|
||||
},
|
||||
recoveryGuardrailsBoundary: {
|
||||
command: "bun scripts/cli.ts check recovery-guardrails",
|
||||
@@ -282,14 +282,16 @@ function codeQueueMgrHealthcheckItem(): CheckItem {
|
||||
}
|
||||
|
||||
function rustCheckItem(): CheckItem {
|
||||
if (process.env.UNIDESK_D601_RUST_CHECK !== "1") {
|
||||
const rustCheckAllowed = process.env.UNIDESK_D601_RUST_CHECK === "1" || process.env.UNIDESK_NATIVE_K3S_RUST_CHECK === "1";
|
||||
if (!rustCheckAllowed) {
|
||||
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",
|
||||
reason: "Rust compilation is intentionally not allowed on the master server; run it from an approved native k3s CI/dev execution plane.",
|
||||
enableOnNativeK3s: "UNIDESK_NATIVE_K3S_RUST_CHECK=1 bun scripts/cli.ts check --rust",
|
||||
legacyEnableOnD601: "UNIDESK_D601_RUST_CHECK=1 bun scripts/cli.ts check --rust",
|
||||
deployPath: "bun scripts/cli.ts deploy apply --env dev --service backend-core",
|
||||
},
|
||||
};
|
||||
@@ -397,6 +399,9 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
|
||||
fileItem("src/components/microservices/auth-broker/src/main.rs"),
|
||||
fileItem("scripts/artifact-consumer-dry-run-matrix-test.ts"),
|
||||
fileItem("src/components/microservices/k3sctl-adapter/k3s/ci/unidesk-ci.pipeline.yaml"),
|
||||
fileItem("src/components/microservices/k3sctl-adapter/k3s/ci/unidesk-ci.pipeline.g14.yaml"),
|
||||
fileItem("src/components/microservices/k3sctl-adapter/k3s/code-queue.g14.k3s.json"),
|
||||
fileItem("src/components/microservices/k3sctl-adapter/k3s/code-queue.g14.k8s.yaml"),
|
||||
);
|
||||
} else {
|
||||
items.push(skippedItem("files:required-entrypoints", "required file presence scan is opt-in", "--files or --full"));
|
||||
@@ -531,7 +536,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
|
||||
if (options.rust) {
|
||||
items.push(rustCheckItem());
|
||||
} else {
|
||||
items.push(skippedItem("rust:backend-core", "Rust check/build must run through D601 CI artifact publication, not on the master server or CD runtime target", "--rust inside D601 CI with UNIDESK_D601_RUST_CHECK=1"));
|
||||
items.push(skippedItem("rust:backend-core", "Rust check/build must run through an approved native k3s CI artifact publication, not on the master server or CD runtime target", "--rust inside native k3s CI with UNIDESK_NATIVE_K3S_RUST_CHECK=1"));
|
||||
}
|
||||
return { ok: items.every((item) => item.ok), mode: options.full ? "full" : "basic", options, items };
|
||||
}
|
||||
|
||||
+201
-69
@@ -27,6 +27,8 @@ const providerGatewayWsEgressProxyUrl = "http://127.0.0.1:18789";
|
||||
const ciCodeQueueImage = "unidesk-code-queue:dev";
|
||||
const codeQueueDirectDockerBaseImage = "unidesk-code-queue:d601";
|
||||
const providerDispatchCompletionLagMs = 45_000;
|
||||
const defaultCiPipelineManifest = "src/components/microservices/k3sctl-adapter/k3s/ci/unidesk-ci.pipeline.yaml";
|
||||
const g14CiPipelineManifest = "src/components/microservices/k3sctl-adapter/k3s/ci/unidesk-ci.pipeline.g14.yaml";
|
||||
const ciRuntimeImages = [
|
||||
"rancher/mirrored-pause:3.6",
|
||||
"rancher/mirrored-library-busybox:1.36.1",
|
||||
@@ -40,10 +42,56 @@ const ciRuntimeImages = [
|
||||
"alpine/git:2.45.2",
|
||||
ciCodeQueueImage,
|
||||
];
|
||||
|
||||
function ciTarget(providerId: string | null): CiTarget {
|
||||
const normalized = providerId ?? d601ProviderId;
|
||||
if (normalized === d601ProviderId) {
|
||||
return {
|
||||
providerId: d601ProviderId,
|
||||
kubeconfig: d601Kubeconfig,
|
||||
hostCwd: "/home/ubuntu",
|
||||
homeDir: "/home/ubuntu",
|
||||
pipelineManifest: defaultCiPipelineManifest,
|
||||
codeQueueImage: ciCodeQueueImage,
|
||||
guardName: "d601_native_k3s_guard",
|
||||
requiredNodeName: "d601",
|
||||
};
|
||||
}
|
||||
if (normalized === "G14") {
|
||||
return {
|
||||
providerId: "G14",
|
||||
kubeconfig: d601Kubeconfig,
|
||||
hostCwd: "/root",
|
||||
homeDir: "/root",
|
||||
pipelineManifest: g14CiPipelineManifest,
|
||||
codeQueueImage: ciCodeQueueImage,
|
||||
guardName: "g14_native_k3s_guard",
|
||||
requiredNodeLabel: { key: "unidesk.ai/node-id", value: "G14" },
|
||||
};
|
||||
}
|
||||
throw new Error(`ci --provider-id currently supports D601 or G14, got ${normalized}`);
|
||||
}
|
||||
|
||||
function providerIdOption(args: string[]): string | null {
|
||||
return stringOption(args, "--provider-id") ?? stringOption(args, "--provider");
|
||||
}
|
||||
interface CiOptions {
|
||||
repoUrl: string;
|
||||
revision: string;
|
||||
waitMs: number;
|
||||
target: CiTarget;
|
||||
}
|
||||
|
||||
interface CiTarget {
|
||||
providerId: string;
|
||||
kubeconfig: string;
|
||||
hostCwd: string;
|
||||
homeDir: string;
|
||||
pipelineManifest: string;
|
||||
codeQueueImage: string;
|
||||
guardName: string;
|
||||
requiredNodeName?: string;
|
||||
requiredNodeLabel?: { key: string; value: string };
|
||||
}
|
||||
|
||||
interface CiPublishBackendCoreOptions {
|
||||
@@ -251,6 +299,70 @@ function shellQuote(value: string): string {
|
||||
return `'${value.replace(/'/gu, "'\\''")}'`;
|
||||
}
|
||||
|
||||
function ciTargetGuardShellLines(target: CiTarget, options: { passOutput?: "stdout" | "stderr" | "quiet" } = {}): string[] {
|
||||
if (target.providerId === d601ProviderId) {
|
||||
return d601K3sGuardShellLines(target.kubeconfig, options);
|
||||
}
|
||||
const passOutput = options.passOutput ?? "stdout";
|
||||
const labelSelector = target.requiredNodeLabel === undefined ? "" : `${target.requiredNodeLabel.key}=${target.requiredNodeLabel.value}`;
|
||||
const passLine = passOutput === "quiet"
|
||||
? ":"
|
||||
: passOutput === "stderr"
|
||||
? `printf '${target.guardName}=pass kubeconfig=%s context=%s server=%s nodes=%s\\n' "$required_kubeconfig" "$context" "$server" "$(printf '%s' "$nodes" | tr '\\n' ',')" >&2`
|
||||
: `printf '${target.guardName}=pass kubeconfig=%s context=%s server=%s nodes=%s\\n' "$required_kubeconfig" "$context" "$server" "$(printf '%s' "$nodes" | tr '\\n' ',')"`;
|
||||
return [
|
||||
`export KUBECONFIG=${shellQuote(target.kubeconfig)}`,
|
||||
"unidesk_ci_k3s_guard() {",
|
||||
` required_kubeconfig=${shellQuote(target.kubeconfig)}`,
|
||||
` required_node=${shellQuote(target.requiredNodeName ?? "")}`,
|
||||
` required_selector=${shellQuote(labelSelector)}`,
|
||||
` guard_name=${shellQuote(target.guardName)}`,
|
||||
" if [ \"${KUBECONFIG:-}\" != \"$required_kubeconfig\" ]; then",
|
||||
" printf '%s=blocked reason=wrong-kubeconfig expected=%s actual=%s\\n' \"$guard_name\" \"$required_kubeconfig\" \"${KUBECONFIG:-<unset>}\" >&2",
|
||||
" return 1",
|
||||
" fi",
|
||||
" if ! command -v kubectl >/dev/null 2>&1; then",
|
||||
" printf '%s=blocked reason=kubectl-missing\\n' \"$guard_name\" >&2",
|
||||
" return 1",
|
||||
" fi",
|
||||
" if ! context=$(kubectl config current-context 2>&1); then",
|
||||
" printf '%s=blocked reason=context-read-failed detail=%s\\n' \"$guard_name\" \"$context\" >&2",
|
||||
" return 1",
|
||||
" fi",
|
||||
" if ! server=$(kubectl config view --minify -o 'jsonpath={.clusters[0].cluster.server}' 2>&1); then",
|
||||
" printf '%s=blocked reason=server-read-failed detail=%s\\n' \"$guard_name\" \"$server\" >&2",
|
||||
" return 1",
|
||||
" fi",
|
||||
" if ! nodes=$(kubectl get nodes -o 'jsonpath={range .items[*]}{.metadata.name}{\"\\n\"}{end}' 2>&1); then",
|
||||
" printf '%s=blocked reason=nodes-read-failed detail=%s\\n' \"$guard_name\" \"$nodes\" >&2",
|
||||
" return 1",
|
||||
" fi",
|
||||
" combined=$(printf '%s\\n%s\\n%s\\n' \"$context\" \"$server\" \"$nodes\")",
|
||||
" if printf '%s\\n' \"$combined\" | grep -Eiq 'docker-desktop|desktop-control-plane|127\\.0\\.0\\.1:11700'; then",
|
||||
" printf '%s=refused reason=forbidden-control-plane context=%s server=%s nodes=%s\\n' \"$guard_name\" \"$context\" \"$server\" \"$(printf '%s' \"$nodes\" | tr '\\n' ',')\" >&2",
|
||||
" return 1",
|
||||
" fi",
|
||||
" if [ -n \"$required_node\" ] && ! printf '%s\\n' \"$nodes\" | grep -Fx \"$required_node\" >/dev/null; then",
|
||||
" printf '%s=blocked reason=missing-required-node expected=%s nodes=%s\\n' \"$guard_name\" \"$required_node\" \"$(printf '%s' \"$nodes\" | tr '\\n' ',')\" >&2",
|
||||
" return 1",
|
||||
" fi",
|
||||
" if [ -n \"$required_selector\" ]; then",
|
||||
" if ! labeled_nodes=$(kubectl get nodes -l \"$required_selector\" -o 'jsonpath={range .items[*]}{.metadata.name}{\"\\n\"}{end}' 2>&1); then",
|
||||
" printf '%s=blocked reason=label-query-failed selector=%s detail=%s\\n' \"$guard_name\" \"$required_selector\" \"$labeled_nodes\" >&2",
|
||||
" return 1",
|
||||
" fi",
|
||||
" if [ -z \"$(printf '%s' \"$labeled_nodes\" | tr -d '\\n')\" ]; then",
|
||||
" printf '%s=blocked reason=missing-required-label selector=%s nodes=%s\\n' \"$guard_name\" \"$required_selector\" \"$(printf '%s' \"$nodes\" | tr '\\n' ',')\" >&2",
|
||||
" return 1",
|
||||
" fi",
|
||||
" nodes=\"$labeled_nodes\"",
|
||||
" fi",
|
||||
` ${passLine}`,
|
||||
"}",
|
||||
"unidesk_ci_k3s_guard",
|
||||
];
|
||||
}
|
||||
|
||||
function safePathToken(value: string): string {
|
||||
return value.replace(/[^a-z0-9._-]/giu, "-").toLowerCase().replace(/^-+|-+$/gu, "").slice(0, 80) || "artifact";
|
||||
}
|
||||
@@ -679,18 +791,18 @@ function requireCiScriptPath(value: unknown): string {
|
||||
return scriptPath;
|
||||
}
|
||||
|
||||
async function dispatchSsh(command: string, waitMs: number, remoteTimeoutMs: number, pollCompletion = true): Promise<DispatchResult> {
|
||||
async function dispatchSsh(command: string, waitMs: number, remoteTimeoutMs: number, pollCompletion = true, target = ciTarget(null)): Promise<DispatchResult> {
|
||||
const dispatchResponse = coreInternalFetch("/api/dispatch", {
|
||||
method: "POST",
|
||||
body: {
|
||||
providerId: d601ProviderId,
|
||||
providerId: target.providerId,
|
||||
command: "host.ssh",
|
||||
payload: {
|
||||
source: "ci-cli",
|
||||
mode: "exec",
|
||||
command,
|
||||
timeoutMs: remoteTimeoutMs,
|
||||
cwd: "/home/ubuntu",
|
||||
cwd: target.hostCwd,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -753,48 +865,48 @@ async function dispatchSsh(command: string, waitMs: number, remoteTimeoutMs: num
|
||||
};
|
||||
}
|
||||
|
||||
async function runRemoteKubectl(script: string, waitMs = 60_000, remoteTimeoutMs = 45_000): Promise<DispatchResult> {
|
||||
const result = await runRemoteKubectlRaw(script, waitMs, remoteTimeoutMs);
|
||||
async function runRemoteKubectl(script: string, waitMs = 60_000, remoteTimeoutMs = 45_000, target = ciTarget(null)): Promise<DispatchResult> {
|
||||
const result = await runRemoteKubectlRaw(script, waitMs, remoteTimeoutMs, target);
|
||||
if (!result.ok) {
|
||||
throw new Error(`D601 kubectl command failed: ${result.stderr || result.stdout || JSON.stringify(result.raw)}`);
|
||||
throw new Error(`${target.providerId} kubectl command failed: ${result.stderr || result.stdout || JSON.stringify(result.raw)}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function runRemoteKubectlRaw(script: string, waitMs = 60_000, remoteTimeoutMs = 45_000): Promise<DispatchResult> {
|
||||
async function runRemoteKubectlRaw(script: string, waitMs = 60_000, remoteTimeoutMs = 45_000, target = ciTarget(null)): Promise<DispatchResult> {
|
||||
const command = [
|
||||
"set -euo pipefail",
|
||||
...d601K3sGuardShellLines(d601Kubeconfig, { passOutput: "stderr" }),
|
||||
...ciTargetGuardShellLines(target, { passOutput: "stderr" }),
|
||||
script,
|
||||
].join("\n");
|
||||
return dispatchSsh(command, waitMs, remoteTimeoutMs);
|
||||
return dispatchSsh(command, waitMs, remoteTimeoutMs, true, target);
|
||||
}
|
||||
|
||||
async function uploadRemoteBase64(path: string, encoded: string): Promise<DispatchResult> {
|
||||
async function uploadRemoteBase64(path: string, encoded: string, target = ciTarget(null)): Promise<DispatchResult> {
|
||||
const init = await dispatchSsh([
|
||||
"set -euo pipefail",
|
||||
`target=${shellQuote(path)}`,
|
||||
"rm -f \"$target\"",
|
||||
": > \"$target\"",
|
||||
"chmod 600 \"$target\"",
|
||||
].join("\n"), 20_000, 10_000);
|
||||
].join("\n"), 20_000, 10_000, true, target);
|
||||
if (!init.ok) return init;
|
||||
for (const chunk of chunks(encoded, 950)) {
|
||||
const append = await dispatchSsh([
|
||||
"set -euo pipefail",
|
||||
`target=${shellQuote(path)}`,
|
||||
`printf %s ${shellQuote(chunk)} >> "$target"`,
|
||||
].join("\n"), 20_000, 10_000);
|
||||
].join("\n"), 20_000, 10_000, true, target);
|
||||
if (!append.ok) return append;
|
||||
}
|
||||
return dispatchSsh([
|
||||
"set -euo pipefail",
|
||||
`target=${shellQuote(path)}`,
|
||||
"wc -c \"$target\"",
|
||||
].join("\n"), 20_000, 10_000);
|
||||
].join("\n"), 20_000, 10_000, true, target);
|
||||
}
|
||||
|
||||
async function runRemoteBackground(label: string, script: string, timeoutMs: number): Promise<DispatchResult> {
|
||||
async function runRemoteBackground(label: string, script: string, timeoutMs: number, target = ciTarget(null)): Promise<DispatchResult> {
|
||||
const token = randomUUID().replace(/-/gu, "").slice(0, 12);
|
||||
const safeLabel = label.replace(/[^a-z0-9-]/giu, "-").toLowerCase().slice(0, 48);
|
||||
const base = `/tmp/unidesk-ci-${safeLabel}-${token}`;
|
||||
@@ -802,7 +914,7 @@ async function runRemoteBackground(label: string, script: string, timeoutMs: num
|
||||
const logPath = `${base}.log`;
|
||||
const donePath = `${base}.done`;
|
||||
const encoded = Buffer.from(script, "utf8").toString("base64");
|
||||
const upload = await uploadRemoteBase64(`${scriptPath}.b64`, encoded);
|
||||
const upload = await uploadRemoteBase64(`${scriptPath}.b64`, encoded, target);
|
||||
if (!upload.ok) return upload;
|
||||
const start = await dispatchSsh([
|
||||
"set -euo pipefail",
|
||||
@@ -815,7 +927,7 @@ async function runRemoteBackground(label: string, script: string, timeoutMs: num
|
||||
"chmod 700 \"$script_path\"",
|
||||
"nohup bash -lc \"bash '$script_path' >'$log_path' 2>&1; code=\\$?; printf '%s\\n' \\\"\\$code\\\" >'$done_path'\" >/tmp/unidesk-ci-nohup.log 2>&1 &",
|
||||
"printf 'remote_job_pid=%s\\nlog=%s\\ndone=%s\\n' \"$!\" \"$log_path\" \"$done_path\"",
|
||||
].join("\n"), 20_000, 10_000);
|
||||
].join("\n"), 20_000, 10_000, true, target);
|
||||
if (!start.ok) return start;
|
||||
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
@@ -833,7 +945,7 @@ async function runRemoteBackground(label: string, script: string, timeoutMs: num
|
||||
" printf 'REMOTE_RUNNING\\n'",
|
||||
"fi",
|
||||
"tail -n 160 \"$log_path\" 2>/dev/null || true",
|
||||
].join("\n"), 75_000, 12_000);
|
||||
].join("\n"), 75_000, 12_000, true, target);
|
||||
if (!latest.ok) {
|
||||
if (latest.status === "timeout" || latest.stderr.includes("did not finish within")) {
|
||||
continue;
|
||||
@@ -860,32 +972,32 @@ async function runRemoteBackground(label: string, script: string, timeoutMs: num
|
||||
};
|
||||
}
|
||||
|
||||
async function remoteApplyManifest(path: string): Promise<void> {
|
||||
async function remoteApplyManifest(path: string, target = ciTarget(null)): Promise<void> {
|
||||
const absolute = rootPath(path);
|
||||
if (!existsSync(absolute)) throw new Error(`manifest not found: ${path}`);
|
||||
const encoded = Buffer.from(readFileSync(absolute, "utf8"), "utf8").toString("base64");
|
||||
const token = randomUUID().replace(/-/gu, "").slice(0, 12);
|
||||
const b64Path = `/tmp/unidesk-ci-apply-${token}.b64`;
|
||||
const upload = await uploadRemoteBase64(b64Path, encoded);
|
||||
const upload = await uploadRemoteBase64(b64Path, encoded, target);
|
||||
if (!upload.ok) throw new Error(`failed to upload manifest ${path}: ${upload.stderr || upload.stdout}`);
|
||||
const script = [
|
||||
"set -euo pipefail",
|
||||
...d601K3sGuardShellLines(d601Kubeconfig),
|
||||
...ciTargetGuardShellLines(target),
|
||||
"tmp=$(mktemp /tmp/unidesk-ci-apply.XXXXXX.yaml)",
|
||||
`b64_path=${shellQuote(b64Path)}`,
|
||||
"trap 'rm -f \"$tmp\" \"$b64_path\"' EXIT",
|
||||
"base64 -d \"$b64_path\" > \"$tmp\"",
|
||||
"kubectl apply -f \"$tmp\"",
|
||||
].join("\n");
|
||||
const result = await runRemoteBackground(`apply-${path.split("/").pop() ?? "manifest"}`, script, 180_000);
|
||||
const result = await runRemoteBackground(`apply-${path.split("/").pop() ?? "manifest"}`, script, 180_000, target);
|
||||
if (!result.ok) throw new Error(`kubectl apply failed for ${path}: ${result.stderr || result.stdout}`);
|
||||
}
|
||||
|
||||
async function prewarmCiRuntimeImages(): Promise<void> {
|
||||
async function prewarmCiRuntimeImages(target = ciTarget(null)): Promise<void> {
|
||||
const images = ciRuntimeImages.map(shellQuote).join(" ");
|
||||
const script = [
|
||||
"set -euo pipefail",
|
||||
...d601K3sGuardShellLines(d601Kubeconfig),
|
||||
...ciTargetGuardShellLines(target),
|
||||
"export DOCKER_CONFIG=/tmp/unidesk-ci-docker-config",
|
||||
"mkdir -p \"$DOCKER_CONFIG\"",
|
||||
"printf '{}\\n' > \"$DOCKER_CONFIG/config.json\"",
|
||||
@@ -900,7 +1012,13 @@ async function prewarmCiRuntimeImages(): Promise<void> {
|
||||
"done",
|
||||
"pause_entrypoint=$(docker image inspect rancher/mirrored-pause:3.6 --format '{{json .Config.Entrypoint}}' 2>/dev/null || true)",
|
||||
"if ! printf '%s' \"$pause_entrypoint\" | grep -q '\"/pause\"'; then echo native_k3s_pause_image_invalid_entrypoint=$pause_entrypoint >&2; exit 1; fi",
|
||||
"containerd_images=$(/mnt/c/Windows/System32/wsl.exe -u root -- ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images ls 2>/tmp/unidesk-ci-containerd-images.err || true)",
|
||||
"root_exec() {",
|
||||
" if [ \"$(id -u)\" = \"0\" ]; then \"$@\"; return $?; fi",
|
||||
" if command -v sudo >/dev/null 2>&1; then sudo \"$@\"; return $?; fi",
|
||||
" if [ -x /mnt/c/Windows/System32/wsl.exe ]; then /mnt/c/Windows/System32/wsl.exe -u root -- \"$@\"; return $?; fi",
|
||||
" \"$@\"",
|
||||
"}",
|
||||
"containerd_images=$(root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images ls 2>/tmp/unidesk-ci-containerd-images.err || true)",
|
||||
"containerd_ready=1",
|
||||
"for image in \"${images[@]}\"; do",
|
||||
" case \"$image\" in",
|
||||
@@ -919,17 +1037,17 @@ async function prewarmCiRuntimeImages(): Promise<void> {
|
||||
"fi",
|
||||
"rm -f /tmp/unidesk-ci-runtime-images.tar",
|
||||
"docker save \"${images[@]}\" -o /tmp/unidesk-ci-runtime-images.tar",
|
||||
"/mnt/c/Windows/System32/wsl.exe -u root -- ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images import --digests --all-platforms /tmp/unidesk-ci-runtime-images.tar >/tmp/unidesk-ci-runtime-images-import.log",
|
||||
"/mnt/c/Windows/System32/wsl.exe -u root -- ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images ls | grep -F 'docker.io/rancher/mirrored-pause:3.6' >/dev/null",
|
||||
"/mnt/c/Windows/System32/wsl.exe -u root -- ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images ls | grep -F 'docker.io/oven/bun:1-debian' >/dev/null",
|
||||
"/mnt/c/Windows/System32/wsl.exe -u root -- ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images ls | grep -F 'docker.io/alpine/git:2.45.2' >/dev/null",
|
||||
`/mnt/c/Windows/System32/wsl.exe -u root -- ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images ls | grep -F ${shellQuote(`docker.io/library/${ciCodeQueueImage}`)} >/dev/null`,
|
||||
"root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images import --digests --all-platforms /tmp/unidesk-ci-runtime-images.tar >/tmp/unidesk-ci-runtime-images-import.log",
|
||||
"root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images ls | grep -F 'docker.io/rancher/mirrored-pause:3.6' >/dev/null",
|
||||
"root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images ls | grep -F 'docker.io/oven/bun:1-debian' >/dev/null",
|
||||
"root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images ls | grep -F 'docker.io/alpine/git:2.45.2' >/dev/null",
|
||||
`root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images ls | grep -F ${shellQuote(`docker.io/library/${target.codeQueueImage}`)} >/dev/null`,
|
||||
].join("\n");
|
||||
const result = await runRemoteBackground("prewarm-runtime-images", script, 900_000);
|
||||
const result = await runRemoteBackground("prewarm-runtime-images", script, 900_000, target);
|
||||
if (!result.ok) throw new Error(`CI runtime image prewarm failed: ${result.stderr || result.stdout}`);
|
||||
}
|
||||
|
||||
async function status(): Promise<Record<string, unknown>> {
|
||||
async function status(target = ciTarget(null)): Promise<Record<string, unknown>> {
|
||||
const summary = await runRemoteKubectl([
|
||||
"set -euo pipefail",
|
||||
"printf 'tekton_pipelines='",
|
||||
@@ -939,10 +1057,10 @@ async function status(): Promise<Record<string, unknown>> {
|
||||
"printf '\\nunidesk_ci='",
|
||||
"kubectl get pipeline,task,pipelinerun,eventlistener,svc -n unidesk-ci -o name 2>/dev/null | tr '\\n' ' ' || true",
|
||||
"printf '\\n'",
|
||||
].join("\n"));
|
||||
].join("\n"), 60_000, 45_000, target);
|
||||
return {
|
||||
ok: true,
|
||||
providerId: d601ProviderId,
|
||||
providerId: target.providerId,
|
||||
orchestrator: "native-k3s",
|
||||
tekton: {
|
||||
pipelineVersion: tektonPipelineVersion,
|
||||
@@ -952,14 +1070,14 @@ async function status(): Promise<Record<string, unknown>> {
|
||||
};
|
||||
}
|
||||
|
||||
async function install(): Promise<Record<string, unknown>> {
|
||||
if (!existsSync(rootPath("src/components/microservices/k3sctl-adapter/k3s/ci/unidesk-ci.pipeline.yaml"))) {
|
||||
async function install(target = ciTarget(null)): Promise<Record<string, unknown>> {
|
||||
if (!existsSync(rootPath(target.pipelineManifest))) {
|
||||
throw new Error("CI manifests are missing");
|
||||
}
|
||||
await prewarmCiRuntimeImages();
|
||||
await prewarmCiRuntimeImages(target);
|
||||
const installTektonScript = [
|
||||
"set -euo pipefail",
|
||||
...d601K3sGuardShellLines(d601Kubeconfig),
|
||||
...ciTargetGuardShellLines(target),
|
||||
`kubectl apply -f ${shellQuote(tektonPipelineReleaseUrl)}`,
|
||||
"kubectl wait --for=condition=Available deployment --all -n tekton-pipelines --timeout=900s",
|
||||
`kubectl apply -f ${shellQuote(tektonTriggersReleaseUrl)}`,
|
||||
@@ -967,12 +1085,12 @@ async function install(): Promise<Record<string, unknown>> {
|
||||
"kubectl wait --for=condition=Available deployment --all -n tekton-pipelines --timeout=900s",
|
||||
"kubectl wait --for=condition=Available deployment --all -n tekton-pipelines-resolvers --timeout=900s",
|
||||
].join("\n");
|
||||
const installTekton = await runRemoteBackground("install-tekton", installTektonScript, 1_200_000);
|
||||
const installTekton = await runRemoteBackground("install-tekton", installTektonScript, 1_200_000, target);
|
||||
if (!installTekton.ok) throw new Error(`Tekton install failed: ${installTekton.stderr || installTekton.stdout}`);
|
||||
await remoteApplyManifest("src/components/microservices/k3sctl-adapter/k3s/ci/tekton-install.yaml");
|
||||
await remoteApplyManifest("src/components/microservices/k3sctl-adapter/k3s/ci/unidesk-ci.pipeline.yaml");
|
||||
await remoteApplyManifest("src/components/microservices/k3sctl-adapter/k3s/ci/unidesk-ci.triggers.yaml");
|
||||
return status();
|
||||
await remoteApplyManifest("src/components/microservices/k3sctl-adapter/k3s/ci/tekton-install.yaml", target);
|
||||
await remoteApplyManifest(target.pipelineManifest, target);
|
||||
await remoteApplyManifest("src/components/microservices/k3sctl-adapter/k3s/ci/unidesk-ci.triggers.yaml", target);
|
||||
return status(target);
|
||||
}
|
||||
|
||||
function pipelineRunManifest(options: CiOptions): string {
|
||||
@@ -1314,11 +1432,11 @@ async function prepareClaudeqqArtifactSource(config: UniDeskConfig, options: CiP
|
||||
};
|
||||
}
|
||||
|
||||
async function remoteCreatePipelineRun(manifest: string): Promise<string> {
|
||||
async function remoteCreatePipelineRun(manifest: string, target = ciTarget(null)): Promise<string> {
|
||||
const encoded = Buffer.from(manifest, "utf8").toString("base64");
|
||||
const token = randomUUID().replace(/-/gu, "").slice(0, 12);
|
||||
const b64Path = `/tmp/unidesk-ci-pipelinerun-${token}.b64`;
|
||||
const upload = await uploadRemoteBase64(b64Path, encoded);
|
||||
const upload = await uploadRemoteBase64(b64Path, encoded, target);
|
||||
if (!upload.ok) throw new Error(`failed to upload PipelineRun manifest: ${upload.stderr || upload.stdout}`);
|
||||
const result = await runRemoteKubectl([
|
||||
"tmp=$(mktemp /tmp/unidesk-ci-run.XXXXXX.yaml)",
|
||||
@@ -1326,15 +1444,15 @@ async function remoteCreatePipelineRun(manifest: string): Promise<string> {
|
||||
"trap 'rm -f \"$tmp\" \"$b64_path\"' EXIT",
|
||||
"base64 -d \"$b64_path\" > \"$tmp\"",
|
||||
"kubectl create -f \"$tmp\" -o jsonpath='{.metadata.name}'",
|
||||
].join("\n"), 60_000, 45_000);
|
||||
].join("\n"), 60_000, 45_000, target);
|
||||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
async function waitForPipelineRun(name: string, waitMs: number): Promise<DispatchResult | null> {
|
||||
async function waitForPipelineRun(name: string, waitMs: number, target = ciTarget(null)): Promise<DispatchResult | null> {
|
||||
if (waitMs <= 0) return null;
|
||||
const command = [
|
||||
"set -euo pipefail",
|
||||
...d601K3sGuardShellLines(d601Kubeconfig),
|
||||
...ciTargetGuardShellLines(target),
|
||||
`printf 'waiting_pipelinerun=%s\\n' ${shellQuote(name)}`,
|
||||
`deadline=$((SECONDS + ${Math.ceil(waitMs / 1000)}))`,
|
||||
"while [ \"$SECONDS\" -lt \"$deadline\" ]; do",
|
||||
@@ -1357,15 +1475,15 @@ async function waitForPipelineRun(name: string, waitMs: number): Promise<Dispatc
|
||||
`kubectl get pipelinerun/${shellQuote(name)} -n unidesk-ci -o json`,
|
||||
"exit 124",
|
||||
].join("\n");
|
||||
return dispatchSsh(command, waitMs + 30_000, waitMs + 20_000);
|
||||
return dispatchSsh(command, waitMs + 30_000, waitMs + 20_000, true, target);
|
||||
}
|
||||
|
||||
async function readPipelineRunCondition(name: string): Promise<PipelineRunCondition> {
|
||||
async function readPipelineRunCondition(name: string, target = ciTarget(null)): Promise<PipelineRunCondition> {
|
||||
const result = await runRemoteKubectlRaw([
|
||||
"set -euo pipefail",
|
||||
`condition="$(kubectl get pipelinerun/${shellQuote(name)} -n unidesk-ci -o jsonpath='{range .status.conditions[?(@.type==\"Succeeded\")]}{.status}{\"\\t\"}{.reason}{\"\\t\"}{.message}{end}' 2>/dev/null || true)"`,
|
||||
"printf '%s\\n' \"$condition\"",
|
||||
].join("\n"), 30_000, 15_000);
|
||||
].join("\n"), 30_000, 15_000, target);
|
||||
const [status = "", reason = "", ...messageParts] = result.stdout.trim().split("\t");
|
||||
const message = messageParts.join("\t");
|
||||
return {
|
||||
@@ -1973,23 +2091,24 @@ async function readArtifactSummaryFromPipelineRun(name: string, context: Artifac
|
||||
return completeArtifactSummaryFromRegistry(parseArtifactSummaryFromOutput(await readPipelineRunLogText(name), context), context);
|
||||
}
|
||||
|
||||
async function readPipelineRunLogText(name: string): Promise<string> {
|
||||
async function readPipelineRunLogText(name: string, target = ciTarget(null)): Promise<string> {
|
||||
const result = await runRemoteKubectlRaw([
|
||||
"set -euo pipefail",
|
||||
`kubectl get pipelinerun/${shellQuote(name)} -n unidesk-ci -o wide`,
|
||||
`kubectl get taskrun -n unidesk-ci -l tekton.dev/pipelineRun=${shellQuote(name)} -o wide`,
|
||||
`for pod in $(kubectl get pods -n unidesk-ci -l tekton.dev/pipelineRun=${shellQuote(name)} -o name); do echo "===== $pod"; kubectl logs -n unidesk-ci "$pod" --all-containers=true --tail=240 || true; done`,
|
||||
].join("\n"), 60_000, 45_000);
|
||||
].join("\n"), 60_000, 45_000, target);
|
||||
return `${result.stdout}\n${result.stderr}`.trim();
|
||||
}
|
||||
|
||||
async function run(options: CiOptions): Promise<Record<string, unknown>> {
|
||||
const name = await remoteCreatePipelineRun(pipelineRunManifest(options));
|
||||
const wait = await waitForPipelineRun(name, options.waitMs);
|
||||
const condition = wait === null ? null : await readPipelineRunCondition(name);
|
||||
const name = await remoteCreatePipelineRun(pipelineRunManifest(options), options.target);
|
||||
const wait = await waitForPipelineRun(name, options.waitMs, options.target);
|
||||
const condition = wait === null ? null : await readPipelineRunCondition(name, options.target);
|
||||
const waitSucceeded = pipelineRunWaitSucceeded(wait, condition);
|
||||
return {
|
||||
ok: waitSucceeded,
|
||||
providerId: options.target.providerId,
|
||||
pipelineRun: name,
|
||||
namespace: "unidesk-ci",
|
||||
repoUrl: options.repoUrl,
|
||||
@@ -2004,8 +2123,8 @@ async function run(options: CiOptions): Promise<Record<string, unknown>> {
|
||||
},
|
||||
condition,
|
||||
next: [
|
||||
`bun scripts/cli.ts ci logs ${name}`,
|
||||
"bun scripts/cli.ts ci status",
|
||||
`bun scripts/cli.ts ci logs ${name} --provider-id ${options.target.providerId}`,
|
||||
`bun scripts/cli.ts ci status --provider-id ${options.target.providerId}`,
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -2582,13 +2701,14 @@ async function runDevE2E(options: CiDevE2EOptions): Promise<Record<string, unkno
|
||||
};
|
||||
}
|
||||
|
||||
async function logs(name: string): Promise<Record<string, unknown>> {
|
||||
async function logs(name: string, target = ciTarget(null)): Promise<Record<string, unknown>> {
|
||||
if (name.length === 0) throw new Error("ci logs requires run id or PipelineRun name");
|
||||
if (/^[a-z0-9]([-a-z0-9]{0,46}[a-z0-9])?$/u.test(name)) {
|
||||
const result = await dispatchSsh([
|
||||
"set -euo pipefail",
|
||||
`run_id=${shellQuote(name)}`,
|
||||
"result_dir=\"/home/ubuntu/.unidesk/runs/$run_id\"",
|
||||
`result_root=${shellQuote(`${target.homeDir}/.unidesk/runs`)}`,
|
||||
"result_dir=\"$result_root/$run_id\"",
|
||||
"printf 'result_dir=%s\\n' \"$result_dir\"",
|
||||
"found=0",
|
||||
"if [ -f \"$result_dir/result.json\" ]; then found=1; echo '===== result.json'; cat \"$result_dir/result.json\"; fi",
|
||||
@@ -2596,7 +2716,7 @@ async function logs(name: string): Promise<Record<string, unknown>> {
|
||||
"if [ -f \"$result_dir/runner.log\" ]; then found=1; echo '===== runner.log'; tail -n 240 \"$result_dir/runner.log\"; fi",
|
||||
"if [ -f \"$result_dir/pods.log\" ]; then found=1; echo '===== pods.log'; tail -n 240 \"$result_dir/pods.log\"; fi",
|
||||
"if [ \"$found\" = \"0\" ]; then echo \"no_run_files=$result_dir\" >&2; exit 42; fi",
|
||||
].join("\n"), 60_000, 45_000);
|
||||
].join("\n"), 60_000, 45_000, true, target);
|
||||
if (result.ok || (result.exitCode !== 42 && !result.stderr.includes("no_run_files="))) {
|
||||
return {
|
||||
ok: result.ok,
|
||||
@@ -2612,9 +2732,10 @@ async function logs(name: string): Promise<Record<string, unknown>> {
|
||||
`kubectl get pipelinerun/${shellQuote(name)} -n unidesk-ci -o wide`,
|
||||
`kubectl get taskrun -n unidesk-ci -l tekton.dev/pipelineRun=${shellQuote(name)} -o wide`,
|
||||
`for pod in $(kubectl get pods -n unidesk-ci -l tekton.dev/pipelineRun=${shellQuote(name)} -o name); do echo "===== $pod"; kubectl logs -n unidesk-ci "$pod" --all-containers=true --tail=160 || true; done`,
|
||||
].join("\n"), 60_000, 45_000);
|
||||
].join("\n"), 60_000, 45_000, target);
|
||||
return {
|
||||
ok: true,
|
||||
providerId: target.providerId,
|
||||
pipelineRun: name,
|
||||
output: result.stdout,
|
||||
stderr: result.stderr,
|
||||
@@ -2649,10 +2770,12 @@ export function ciHelp(): Record<string, unknown> {
|
||||
const catalog = loadCiCatalog();
|
||||
return {
|
||||
command: "ci install|status|run|publish-backend-core|publish-user-service|run-dev-e2e|logs",
|
||||
description: "Manage the D601 k3s Tekton CI gate. CI may publish commit-pinned image artifacts, but it intentionally does not deploy CD.",
|
||||
description: "Manage native k3s Tekton CI on D601 or G14. CI may publish commit-pinned image artifacts, but it intentionally does not deploy CD.",
|
||||
examples: [
|
||||
"bun scripts/cli.ts ci install",
|
||||
"bun scripts/cli.ts ci install --provider-id G14",
|
||||
"bun scripts/cli.ts ci run --revision <commit>",
|
||||
"bun scripts/cli.ts ci run --provider-id G14 --revision <commit>",
|
||||
"bun scripts/cli.ts ci publish-backend-core --commit <full-sha>",
|
||||
"bun scripts/cli.ts ci publish-user-service --service baidu-netdisk --commit <full-sha>",
|
||||
"bun scripts/cli.ts ci publish-user-service --service mdtodo --commit <full-sha>",
|
||||
@@ -2661,7 +2784,7 @@ export function ciHelp(): Record<string, unknown> {
|
||||
"bun scripts/cli.ts ci publish-user-service --service decision-center --commit <full-sha>",
|
||||
"bun scripts/cli.ts ci publish-user-service --service frontend --commit <full-sha>",
|
||||
"bun scripts/cli.ts ci run-dev-e2e --wait-ms 600000",
|
||||
"bun scripts/cli.ts ci logs <runId>",
|
||||
"bun scripts/cli.ts ci logs <runId> [--provider-id G14]",
|
||||
],
|
||||
tekton: {
|
||||
pipelineVersion: tektonPipelineVersion,
|
||||
@@ -2671,6 +2794,14 @@ export function ciHelp(): Record<string, unknown> {
|
||||
triggers: tektonTriggersReleaseUrl,
|
||||
interceptors: tektonTriggersInterceptorsUrl,
|
||||
},
|
||||
targets: {
|
||||
default: "D601",
|
||||
g14: {
|
||||
providerId: "G14",
|
||||
pipelineManifest: g14CiPipelineManifest,
|
||||
nodeSelector: "unidesk.ai/node-id=G14",
|
||||
},
|
||||
},
|
||||
},
|
||||
backendCoreArtifact: {
|
||||
producer: "D601 CI",
|
||||
@@ -2716,13 +2847,14 @@ function requireRunId(value: string): string {
|
||||
export async function runCiCommand(config: UniDeskConfig, args: string[]): Promise<Record<string, unknown>> {
|
||||
const [action = "status", nameArg] = args;
|
||||
if (isHelpArg(action) || args.slice(1).some(isHelpArg)) return ciHelp();
|
||||
if (action === "install") return install();
|
||||
if (action === "status") return status();
|
||||
if (action === "install") return install(ciTarget(providerIdOption(args)));
|
||||
if (action === "status") return status(ciTarget(providerIdOption(args)));
|
||||
if (action === "run") {
|
||||
const target = ciTarget(providerIdOption(args));
|
||||
const repoUrl = stringOption(args, "--repo") ?? stringOption(args, "--repo-url") ?? "https://github.com/pikasTech/unidesk";
|
||||
const revision = requireRevision(stringOption(args, "--revision") ?? stringOption(args, "--commit"));
|
||||
const waitMs = numberOption(args, "--wait-ms", 0);
|
||||
return run({ repoUrl, revision, waitMs });
|
||||
return run({ repoUrl, revision, waitMs, target });
|
||||
}
|
||||
if (action === "publish-backend-core") {
|
||||
if (stringOption(args, "--repo") !== null || stringOption(args, "--repo-url") !== null) {
|
||||
@@ -2799,11 +2931,11 @@ export async function runCiCommand(config: UniDeskConfig, args: string[]): Promi
|
||||
waitMs,
|
||||
});
|
||||
}
|
||||
if (action === "logs") return logs(nameArg ?? "");
|
||||
if (action === "logs") return logs(nameArg ?? "", ciTarget(providerIdOption(args)));
|
||||
throw new Error("ci command must be one of: install, status, run, publish-backend-core, publish-user-service, run-dev-e2e, logs");
|
||||
}
|
||||
|
||||
export function startCiInstallJob(): Record<string, unknown> {
|
||||
const job = startJob("ci_install", ["bun", "scripts/cli.ts", "ci", "install"], "Install/refresh Tekton CI on D601 k3s");
|
||||
export function startCiInstallJob(providerId = d601ProviderId): Record<string, unknown> {
|
||||
const job = startJob("ci_install", ["bun", "scripts/cli.ts", "ci", "install", "--provider-id", providerId], `Install/refresh Tekton CI on ${providerId} native k3s`);
|
||||
return { ok: true, job };
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ const defaultPrivateKeyPath = "/root/.ssh/id_ed25519";
|
||||
const defaultKnownHostsPath = "/root/.ssh/known_hosts";
|
||||
const providerGatewayWsEgressProxyUrl = "http://127.0.0.1:18789";
|
||||
|
||||
function providerHomeDir(providerId: string): string {
|
||||
return providerId === "G14" ? "/root" : "/home/ubuntu";
|
||||
}
|
||||
|
||||
function pgLiteral(value: string): string {
|
||||
return `'${value.replace(/'/gu, "''")}'`;
|
||||
}
|
||||
@@ -202,7 +206,8 @@ import subprocess
|
||||
import sys
|
||||
|
||||
data = json.load(sys.stdin)
|
||||
ssh_dir = pathlib.Path("/home/ubuntu/.ssh")
|
||||
home_dir = pathlib.Path(str(data.get("homeDir") or os.environ.get("HOME") or "/home/ubuntu"))
|
||||
ssh_dir = home_dir / ".ssh"
|
||||
private_key = str(data.get("privateKey") or "")
|
||||
public_key = str(data.get("publicKey") or "").strip()
|
||||
known_hosts = str(data.get("knownHosts") or "")
|
||||
@@ -321,6 +326,7 @@ function distributeGithubIdentity(providerId: string, identity: GithubSshIdentit
|
||||
privateKey: identity.privateKey,
|
||||
publicKey: identity.publicKey,
|
||||
knownHosts: identity.knownHosts,
|
||||
homeDir: providerHomeDir(providerId),
|
||||
});
|
||||
const remotePython = remoteInstallPythonSource();
|
||||
const proxyPython = gitSshHttpConnectProxySource();
|
||||
|
||||
+14
-6
@@ -50,7 +50,7 @@ export function unsupportedRebuildService(value: string | undefined): Record<str
|
||||
},
|
||||
"code-queue": {
|
||||
classification: "standard-artifact",
|
||||
reason: "code-queue execution plane is D601 k3s-managed and only supports the dev artifact consumer in this phase",
|
||||
reason: "code-queue execution plane is native-k3s-managed and only supports the dev artifact consumer in this phase",
|
||||
replacement: "deploy apply --env dev --service code-queue or artifact-registry deploy-service --env dev --service code-queue",
|
||||
deleteAllowedLater: false,
|
||||
},
|
||||
@@ -138,6 +138,14 @@ export function writeComposeEnv(config: UniDeskConfig, freshLogPrefix: boolean):
|
||||
const labels = JSON.stringify(config.providerGateway.labels);
|
||||
const microservices = JSON.stringify(config.microservices);
|
||||
const restrictedHostBind = config.network.restrictedHostAccess?.bindHost || "127.0.0.1";
|
||||
const codeQueueService = config.microservices.find((service) => service.id === "code-queue");
|
||||
const codeQueueProviderId = codeQueueService?.providerId || "D601";
|
||||
const codeQueueRemoteWorkdir = codeQueueService?.development.worktreePath || "/home/ubuntu";
|
||||
const codeQueueExecutionProviderIds = codeQueueService?.deployment.expectedNodeIds?.join(",") || codeQueueProviderId;
|
||||
const codeQueueEnvValue = (key: string, defaultValue: string): string => {
|
||||
const value = process.env[key];
|
||||
return value === undefined || value.length === 0 ? defaultValue : value;
|
||||
};
|
||||
const lines = {
|
||||
UNIDESK_PUBLIC_HOST: config.network.publicHost,
|
||||
UNIDESK_CORE_PORT: String(config.network.core.port),
|
||||
@@ -233,16 +241,16 @@ export function writeComposeEnv(config: UniDeskConfig, freshLogPrefix: boolean):
|
||||
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_REMOTE_WORKDIR: codeQueueEnvValue("UNIDESK_CODE_QUEUE_REMOTE_WORKDIR", codeQueueRemoteWorkdir),
|
||||
UNIDESK_CODE_QUEUE_MAIN_PROVIDER_ID: codeQueueEnvValue("UNIDESK_CODE_QUEUE_MAIN_PROVIDER_ID", codeQueueProviderId),
|
||||
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_EXECUTION_PROVIDER_IDS: codeQueueEnvValue("UNIDESK_CODE_QUEUE_EXECUTION_PROVIDER_IDS", codeQueueExecutionProviderIds),
|
||||
UNIDESK_CODE_QUEUE_DEV_CONTAINER_DEFAULT_PROVIDER_ID: codeQueueEnvValue("UNIDESK_CODE_QUEUE_DEV_CONTAINER_DEFAULT_PROVIDER_ID", codeQueueService?.development.providerId || codeQueueProviderId),
|
||||
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_DEV_CONTAINER_WORKDIR: codeQueueEnvValue("UNIDESK_CODE_QUEUE_DEV_CONTAINER_WORKDIR", codeQueueRemoteWorkdir),
|
||||
UNIDESK_OA_EVENT_FLOW_BASE_URL: runtimeSecret("UNIDESK_OA_EVENT_FLOW_BASE_URL") || "http://oa-event-flow:4255",
|
||||
UNIDESK_OA_EVENT_FLOW_PORT: runtimeSecret("UNIDESK_OA_EVENT_FLOW_PORT") || "4255",
|
||||
UNIDESK_OA_EVENT_FLOW_BIND_HOST: runtimeSecretWithDefault("UNIDESK_OA_EVENT_FLOW_BIND_HOST", restrictedHostBind, "127.0.0.1"),
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ export function rootHelp(): unknown {
|
||||
{ command: "debug dispatch [providerId] [docker.ps|provider.upgrade|host.ssh|microservice.http|echo] [--wait-ms N]", description: "Submit a real internal-core dispatch request for CLI debugging." },
|
||||
{ command: "debug task <taskId|latest>", description: "Read a dispatched task record from internal core for CLI debugging." },
|
||||
{ command: "network perf [--service code-queue --path /api/tasks/overview?limit=30 --count N --concurrency N --label before|after]", description: "Benchmark frontend -> backend-core -> provider/adapter user-service networking and report latency/proxy-mode distributions." },
|
||||
{ command: "ci install|status|run|publish-backend-core|publish-user-service|run-dev-e2e|logs", description: "Manage D601 k3s Tekton CI; artifact publish commands build commit-pinned images in CI without deploying CD." },
|
||||
{ command: "ci install|status|run|publish-backend-core|publish-user-service|run-dev-e2e|logs", description: "Manage D601/G14 native k3s Tekton CI; artifact publish commands build commit-pinned images in CI without deploying CD." },
|
||||
{ command: "e2e run [--only pattern[,pattern...]] [--skip pattern[,pattern...]]", description: "Run selected public/internal/Playwright E2E checks; use --only for focused iteration and rerun without filters for final regression." },
|
||||
{ command: "bun scripts/playwright-cli.ts screenshot|open|eval ...", description: "Repo-owned Playwright wrapper for commander browser checks; headless by default, supports storage-state --session, and returns JSON guidance for unsupported interactive daemon commands." },
|
||||
],
|
||||
|
||||
@@ -63,8 +63,9 @@ fn read_microservices_env() -> anyhow::Result<Vec<MicroserviceConfig>> {
|
||||
}
|
||||
if service.deployment.mode != "unidesk-direct"
|
||||
&& service.deployment.mode != "k3sctl-managed"
|
||||
&& service.deployment.mode != "internal-sidecar"
|
||||
{
|
||||
bail!("MICROSERVICES_JSON[{index}].deployment.mode must be unidesk-direct or k3sctl-managed");
|
||||
bail!("MICROSERVICES_JSON[{index}].deployment.mode must be unidesk-direct, k3sctl-managed, or internal-sidecar");
|
||||
}
|
||||
service.backend.allowed_methods = service
|
||||
.backend
|
||||
|
||||
@@ -175,7 +175,15 @@ fn json_i64(value: &Value, path: &[&str]) -> Option<i64> {
|
||||
current.as_i64()
|
||||
}
|
||||
|
||||
fn code_queue_route_postgres_check(scheduler_body: &Value) -> Value {
|
||||
fn code_queue_infra_service_names(service: &MicroserviceConfig) -> (String, String) {
|
||||
let provider_slug = service.provider_id.to_ascii_lowercase();
|
||||
(
|
||||
format!("{provider_slug}-provider-egress-proxy"),
|
||||
format!("{provider_slug}-tcp-egress-gateway"),
|
||||
)
|
||||
}
|
||||
|
||||
fn code_queue_route_postgres_check(scheduler_body: &Value, tcp_egress_service: &str) -> Value {
|
||||
let storage = scheduler_body
|
||||
.get("queue")
|
||||
.and_then(|queue| queue.get("storage"))
|
||||
@@ -187,7 +195,7 @@ fn code_queue_route_postgres_check(scheduler_body: &Value) -> Value {
|
||||
let last_error = storage.get("lastError").cloned().unwrap_or(Value::Null);
|
||||
json!({
|
||||
"ok": postgres_ready && last_error.is_null(),
|
||||
"route": "d601-tcp-egress-gateway.unidesk.svc.cluster.local:15432",
|
||||
"route": format!("{tcp_egress_service}.unidesk.svc.cluster.local:15432"),
|
||||
"postgresReady": postgres_ready,
|
||||
"lastError": last_error,
|
||||
"source": "scheduler.health.queue.storage"
|
||||
@@ -1746,17 +1754,18 @@ async fn code_queue_health_response(
|
||||
&& scheduler_body.get("ok").and_then(Value::as_bool) != Some(false);
|
||||
let scheduler_diagnostics =
|
||||
code_queue_adapter_diagnostics(state, service, "code-queue-scheduler").await;
|
||||
let (provider_egress_service, tcp_egress_service) = code_queue_infra_service_names(service);
|
||||
let provider_egress_diagnostics =
|
||||
code_queue_adapter_diagnostics(state, service, "d601-provider-egress-proxy").await;
|
||||
code_queue_adapter_diagnostics(state, service, &provider_egress_service).await;
|
||||
let tcp_egress_diagnostics =
|
||||
code_queue_adapter_diagnostics(state, service, "d601-tcp-egress-gateway").await;
|
||||
code_queue_adapter_diagnostics(state, service, &tcp_egress_service).await;
|
||||
let scheduler_dependency =
|
||||
code_queue_dependency_check("code-queue-scheduler", &scheduler_diagnostics);
|
||||
let provider_egress =
|
||||
code_queue_dependency_check("d601-provider-egress-proxy", &provider_egress_diagnostics);
|
||||
code_queue_dependency_check(&provider_egress_service, &provider_egress_diagnostics);
|
||||
let tcp_egress =
|
||||
code_queue_dependency_check("d601-tcp-egress-gateway", &tcp_egress_diagnostics);
|
||||
let postgres_route = code_queue_route_postgres_check(&scheduler_body);
|
||||
code_queue_dependency_check(&tcp_egress_service, &tcp_egress_diagnostics);
|
||||
let postgres_route = code_queue_route_postgres_check(&scheduler_body, &tcp_egress_service);
|
||||
let stale_reconcile = code_queue_reconcile_check(&scheduler_body);
|
||||
let dependencies_ok = scheduler_dependency
|
||||
.get("ok")
|
||||
@@ -1791,15 +1800,15 @@ async fn code_queue_health_response(
|
||||
"scheduler": { "ok": scheduler_healthy, "status": scheduler_status, "body": scheduler_body, "timedOut": false, "requiredFor": ["running task steer", "running task interrupt", "scheduler claim/runner execution", "dev-container control"] },
|
||||
"checks": {
|
||||
"scheduler": scheduler_dependency,
|
||||
"d601ProviderEgressProxy": provider_egress,
|
||||
"d601TcpEgressGateway": tcp_egress,
|
||||
"providerEgressProxy": provider_egress,
|
||||
"tcpEgressGateway": tcp_egress,
|
||||
"postgresRoute": postgres_route,
|
||||
"staleActiveReconcile": stale_reconcile
|
||||
},
|
||||
"diagnostics": {
|
||||
"scheduler": scheduler_diagnostics,
|
||||
"d601ProviderEgressProxy": provider_egress_diagnostics,
|
||||
"d601TcpEgressGateway": tcp_egress_diagnostics
|
||||
"providerEgressProxy": provider_egress_diagnostics,
|
||||
"tcpEgressGateway": tcp_egress_diagnostics
|
||||
},
|
||||
});
|
||||
if head_only {
|
||||
@@ -1981,17 +1990,18 @@ async fn k3sctl_managed_diagnostics_response(
|
||||
.unwrap_or(Value::Null);
|
||||
let scheduler_diagnostics =
|
||||
code_queue_adapter_diagnostics(state, service, "code-queue-scheduler").await;
|
||||
let (provider_egress_service, tcp_egress_service) = code_queue_infra_service_names(service);
|
||||
let provider_egress_diagnostics =
|
||||
code_queue_adapter_diagnostics(state, service, "d601-provider-egress-proxy").await;
|
||||
code_queue_adapter_diagnostics(state, service, &provider_egress_service).await;
|
||||
let tcp_egress_diagnostics =
|
||||
code_queue_adapter_diagnostics(state, service, "d601-tcp-egress-gateway").await;
|
||||
code_queue_adapter_diagnostics(state, service, &tcp_egress_service).await;
|
||||
let scheduler_dependency =
|
||||
code_queue_dependency_check("code-queue-scheduler", &scheduler_diagnostics);
|
||||
let provider_egress =
|
||||
code_queue_dependency_check("d601-provider-egress-proxy", &provider_egress_diagnostics);
|
||||
code_queue_dependency_check(&provider_egress_service, &provider_egress_diagnostics);
|
||||
let tcp_egress =
|
||||
code_queue_dependency_check("d601-tcp-egress-gateway", &tcp_egress_diagnostics);
|
||||
let postgres_route = code_queue_route_postgres_check(&scheduler_body);
|
||||
code_queue_dependency_check(&tcp_egress_service, &tcp_egress_diagnostics);
|
||||
let postgres_route = code_queue_route_postgres_check(&scheduler_body, &tcp_egress_service);
|
||||
let stale_reconcile = code_queue_reconcile_check(&scheduler_body);
|
||||
code_queue_ok = scheduler_dependency
|
||||
.get("ok")
|
||||
@@ -2015,20 +2025,20 @@ async fn k3sctl_managed_diagnostics_response(
|
||||
.unwrap_or(false);
|
||||
code_queue_checks = json!({
|
||||
"scheduler": scheduler_dependency,
|
||||
"d601ProviderEgressProxy": provider_egress,
|
||||
"d601TcpEgressGateway": tcp_egress,
|
||||
"providerEgressProxy": provider_egress,
|
||||
"tcpEgressGateway": tcp_egress,
|
||||
"postgresRoute": postgres_route,
|
||||
"staleActiveReconcile": stale_reconcile
|
||||
});
|
||||
code_queue_dependencies = json!({
|
||||
"postgresRoute": "d601-tcp-egress-gateway.unidesk.svc.cluster.local:15432",
|
||||
"requiredDeployments": ["d601-tcp-egress-gateway", "d601-provider-egress-proxy", "code-queue"],
|
||||
"requiredEndpoints": ["d601-tcp-egress-gateway", "d601-provider-egress-proxy", "code-queue-scheduler"]
|
||||
"postgresRoute": format!("{tcp_egress_service}.unidesk.svc.cluster.local:15432"),
|
||||
"requiredDeployments": [tcp_egress_service, provider_egress_service, "code-queue".to_string()],
|
||||
"requiredEndpoints": [tcp_egress_service, provider_egress_service, "code-queue-scheduler".to_string()]
|
||||
});
|
||||
code_queue_adapter = json!({
|
||||
"scheduler": scheduler_diagnostics,
|
||||
"d601ProviderEgressProxy": provider_egress_diagnostics,
|
||||
"d601TcpEgressGateway": tcp_egress_diagnostics
|
||||
"providerEgressProxy": provider_egress_diagnostics,
|
||||
"tcpEgressGateway": tcp_egress_diagnostics
|
||||
});
|
||||
}
|
||||
let ok = (200..300).contains(&status2)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
services:
|
||||
k3sctl-adapter-g14:
|
||||
image: unidesk-k3sctl-adapter:g14
|
||||
build:
|
||||
context: ../../../..
|
||||
dockerfile: src/components/microservices/k3sctl-adapter/Dockerfile
|
||||
args:
|
||||
K3SCTL_ADAPTER_BASE_IMAGE: ${K3SCTL_ADAPTER_BASE_IMAGE:-oven/bun:1-debian}
|
||||
container_name: k3sctl-adapter-g14
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- path: ${K3SCTL_ADAPTER_ENV_FILE:-../../../../.state/k3sctl-adapter-g14.env}
|
||||
required: false
|
||||
ports:
|
||||
- "127.0.0.1:${K3SCTL_ADAPTER_HOST_PORT:-4266}:4266"
|
||||
environment:
|
||||
HOST: "0.0.0.0"
|
||||
PORT: "4266"
|
||||
LOG_FILE: "/var/log/unidesk/k3sctl-adapter-g14.jsonl"
|
||||
K3SCTL_CLUSTER_ID: "${K3SCTL_CLUSTER_ID:-unidesk-k3s-g14}"
|
||||
K3SCTL_NODE_ID: "${K3SCTL_NODE_ID:-G14}"
|
||||
K3SCTL_KUBECTL_ENABLED: "${K3SCTL_KUBECTL_ENABLED:-false}"
|
||||
K3SCTL_KUBE_API_PROXY_ENABLED: "${K3SCTL_KUBE_API_PROXY_ENABLED:-true}"
|
||||
K3SCTL_KUBECONFIG_PATH: "/var/lib/unidesk/k3s/kubeconfig"
|
||||
K3SCTL_KUBE_API_CONNECT_HOST: "${K3SCTL_KUBE_API_CONNECT_HOST:-host.docker.internal}"
|
||||
K3SCTL_KUBE_API_SSH_TUNNEL_ENABLED: "${K3SCTL_KUBE_API_SSH_TUNNEL_ENABLED:-false}"
|
||||
K3SCTL_KUBE_API_SSH_HOST: "${K3SCTL_KUBE_API_SSH_HOST:-host.docker.internal}"
|
||||
K3SCTL_KUBE_API_SSH_USER: "${K3SCTL_KUBE_API_SSH_USER:-root}"
|
||||
K3SCTL_KUBE_API_SSH_KEY: "${K3SCTL_KUBE_API_SSH_KEY:-/run/host-ssh/id_ed25519}"
|
||||
K3SCTL_KUBE_API_LOCAL_HOST: "${K3SCTL_KUBE_API_LOCAL_HOST:-127.0.0.1}"
|
||||
K3SCTL_KUBE_API_LOCAL_PORT: "${K3SCTL_KUBE_API_LOCAL_PORT:-6443}"
|
||||
K3SCTL_KUBE_API_REMOTE_HOST: "${K3SCTL_KUBE_API_REMOTE_HOST:-127.0.0.1}"
|
||||
K3SCTL_KUBE_API_REMOTE_PORT: "${K3SCTL_KUBE_API_REMOTE_PORT:-6443}"
|
||||
K3SCTL_NATIVE_SERVICE_PROXY_ENABLED: "${K3SCTL_NATIVE_SERVICE_PROXY_ENABLED:-true}"
|
||||
K3SCTL_NATIVE_SERVICE_SSH_TUNNEL_ENABLED: "${K3SCTL_NATIVE_SERVICE_SSH_TUNNEL_ENABLED:-true}"
|
||||
K3SCTL_NATIVE_SERVICE_PROBE_TIMEOUT_MS: "${K3SCTL_NATIVE_SERVICE_PROBE_TIMEOUT_MS:-1200}"
|
||||
K3SCTL_NATIVE_SERVICE_FAILURE_COOLDOWN_MS: "${K3SCTL_NATIVE_SERVICE_FAILURE_COOLDOWN_MS:-10000}"
|
||||
K3SCTL_NATIVE_SERVICE_RESOLUTION_TTL_MS: "${K3SCTL_NATIVE_SERVICE_RESOLUTION_TTL_MS:-30000}"
|
||||
K3SCTL_NATIVE_SERVICE_TUNNEL_CONNECT_TIMEOUT_MS: "${K3SCTL_NATIVE_SERVICE_TUNNEL_CONNECT_TIMEOUT_MS:-3000}"
|
||||
K3SCTL_NATIVE_SERVICE_URL_CODE_QUEUE: "${K3SCTL_NATIVE_SERVICE_URL_CODE_QUEUE:-}"
|
||||
K3SCTL_NATIVE_SERVICE_URL_CODE_QUEUE_READ: "${K3SCTL_NATIVE_SERVICE_URL_CODE_QUEUE_READ:-}"
|
||||
K3SCTL_NATIVE_SERVICE_URL_CODE_QUEUE_WRITE: "${K3SCTL_NATIVE_SERVICE_URL_CODE_QUEUE_WRITE:-}"
|
||||
K3SCTL_NATIVE_SERVICE_URL_CODE_QUEUE_SCHEDULER: "${K3SCTL_NATIVE_SERVICE_URL_CODE_QUEUE_SCHEDULER:-}"
|
||||
K3SCTL_MANIFEST_PATHS: "${K3SCTL_MANIFEST_PATHS:-k3s/code-queue.g14.k3s.json}"
|
||||
K3SCTL_SERVICES_JSON: "${K3SCTL_SERVICES_JSON:-[]}"
|
||||
UNIDESK_LOG_RETENTION_BYTES: "${UNIDESK_LOG_RETENTION_BYTES:-512MiB}"
|
||||
volumes:
|
||||
- ${K3SCTL_ADAPTER_LOG_DIR:-../../../../.state/k3sctl-adapter-g14/logs}:/var/log/unidesk
|
||||
- ${K3SCTL_KUBECONFIG_HOST_PATH:-/etc/rancher/k3s/k3s.yaml}:/var/lib/unidesk/k3s/kubeconfig:ro
|
||||
- ${K3SCTL_HOST_SSH_KEY_DIR:-/root/.unidesk/host-ssh}:/run/host-ssh:ro
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
networks:
|
||||
- default
|
||||
- provider-gateway
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS --max-time 2 http://127.0.0.1:4266/health >/dev/null"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 20
|
||||
|
||||
networks:
|
||||
provider-gateway:
|
||||
external: true
|
||||
name: ${K3SCTL_PROVIDER_GATEWAY_NETWORK:-unidesk-g14_default}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,228 @@
|
||||
[
|
||||
{
|
||||
"apiVersion": "unidesk.ai/k3s/v1",
|
||||
"kind": "ManagedKubernetesService",
|
||||
"metadata": {
|
||||
"name": "code-queue",
|
||||
"namespace": "unidesk"
|
||||
},
|
||||
"spec": {
|
||||
"adapterServiceId": "k3sctl-adapter-g14",
|
||||
"controlPlane": {
|
||||
"type": "kubernetes",
|
||||
"cluster": "unidesk-k3s",
|
||||
"context": "unidesk-k3s"
|
||||
},
|
||||
"route": {
|
||||
"kind": "kubernetes-service",
|
||||
"serviceName": "code-queue-scheduler",
|
||||
"servicePort": 4222,
|
||||
"deploymentName": "code-queue"
|
||||
},
|
||||
"activeInstanceId": "G14",
|
||||
"singleWriter": true,
|
||||
"expectedNodeIds": [
|
||||
"G14"
|
||||
],
|
||||
"instances": [
|
||||
{
|
||||
"id": "G14",
|
||||
"nodeId": "G14",
|
||||
"role": "primary",
|
||||
"baseUrl": "kubernetes://unidesk/services/code-queue-scheduler:4222",
|
||||
"healthPath": "/health",
|
||||
"healthMode": "service-proxy"
|
||||
}
|
||||
],
|
||||
"requireAllInstancesHealthy": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "unidesk.ai/k3s/v1",
|
||||
"kind": "ManagedKubernetesService",
|
||||
"metadata": {
|
||||
"name": "code-queue-read",
|
||||
"namespace": "unidesk"
|
||||
},
|
||||
"spec": {
|
||||
"adapterServiceId": "k3sctl-adapter-g14",
|
||||
"controlPlane": {
|
||||
"type": "kubernetes",
|
||||
"cluster": "unidesk-k3s",
|
||||
"context": "unidesk-k3s"
|
||||
},
|
||||
"route": {
|
||||
"kind": "kubernetes-service",
|
||||
"serviceName": "code-queue-read",
|
||||
"servicePort": 4222
|
||||
},
|
||||
"activeInstanceId": "G14-read",
|
||||
"singleWriter": false,
|
||||
"expectedNodeIds": [
|
||||
"G14"
|
||||
],
|
||||
"instances": [
|
||||
{
|
||||
"id": "G14-read",
|
||||
"nodeId": "G14",
|
||||
"role": "standby",
|
||||
"baseUrl": "kubernetes://unidesk/services/code-queue-read:4222",
|
||||
"healthPath": "/live",
|
||||
"healthMode": "service-proxy"
|
||||
}
|
||||
],
|
||||
"requireAllInstancesHealthy": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "unidesk.ai/k3s/v1",
|
||||
"kind": "ManagedKubernetesService",
|
||||
"metadata": {
|
||||
"name": "code-queue-write",
|
||||
"namespace": "unidesk"
|
||||
},
|
||||
"spec": {
|
||||
"adapterServiceId": "k3sctl-adapter-g14",
|
||||
"controlPlane": {
|
||||
"type": "kubernetes",
|
||||
"cluster": "unidesk-k3s",
|
||||
"context": "unidesk-k3s"
|
||||
},
|
||||
"route": {
|
||||
"kind": "kubernetes-service",
|
||||
"serviceName": "code-queue-write",
|
||||
"servicePort": 4222
|
||||
},
|
||||
"activeInstanceId": "G14-write",
|
||||
"singleWriter": true,
|
||||
"expectedNodeIds": [
|
||||
"G14"
|
||||
],
|
||||
"instances": [
|
||||
{
|
||||
"id": "G14-write",
|
||||
"nodeId": "G14",
|
||||
"role": "primary",
|
||||
"baseUrl": "kubernetes://unidesk/services/code-queue-write:4222",
|
||||
"healthPath": "/health",
|
||||
"healthMode": "service-proxy"
|
||||
}
|
||||
],
|
||||
"requireAllInstancesHealthy": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "unidesk.ai/k3s/v1",
|
||||
"kind": "ManagedKubernetesService",
|
||||
"metadata": {
|
||||
"name": "code-queue-scheduler",
|
||||
"namespace": "unidesk"
|
||||
},
|
||||
"spec": {
|
||||
"adapterServiceId": "k3sctl-adapter-g14",
|
||||
"controlPlane": {
|
||||
"type": "kubernetes",
|
||||
"cluster": "unidesk-k3s",
|
||||
"context": "unidesk-k3s"
|
||||
},
|
||||
"route": {
|
||||
"kind": "kubernetes-service",
|
||||
"serviceName": "code-queue-scheduler",
|
||||
"servicePort": 4222,
|
||||
"deploymentName": "code-queue"
|
||||
},
|
||||
"activeInstanceId": "G14-scheduler",
|
||||
"singleWriter": true,
|
||||
"expectedNodeIds": [
|
||||
"G14"
|
||||
],
|
||||
"instances": [
|
||||
{
|
||||
"id": "G14-scheduler",
|
||||
"nodeId": "G14",
|
||||
"role": "primary",
|
||||
"baseUrl": "kubernetes://unidesk/services/code-queue-scheduler:4222",
|
||||
"healthPath": "/health",
|
||||
"healthMode": "service-proxy"
|
||||
}
|
||||
],
|
||||
"requireAllInstancesHealthy": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "unidesk.ai/k3s/v1",
|
||||
"kind": "ManagedKubernetesService",
|
||||
"metadata": {
|
||||
"name": "g14-provider-egress-proxy",
|
||||
"namespace": "unidesk"
|
||||
},
|
||||
"spec": {
|
||||
"adapterServiceId": "k3sctl-adapter-g14",
|
||||
"controlPlane": {
|
||||
"type": "kubernetes",
|
||||
"cluster": "unidesk-k3s",
|
||||
"context": "unidesk-k3s"
|
||||
},
|
||||
"route": {
|
||||
"kind": "kubernetes-service",
|
||||
"serviceName": "g14-provider-egress-proxy",
|
||||
"servicePort": 18789,
|
||||
"deploymentName": "g14-provider-egress-proxy"
|
||||
},
|
||||
"activeInstanceId": "G14-provider-egress",
|
||||
"singleWriter": false,
|
||||
"expectedNodeIds": [
|
||||
"G14"
|
||||
],
|
||||
"instances": [
|
||||
{
|
||||
"id": "G14-provider-egress",
|
||||
"nodeId": "G14",
|
||||
"role": "primary",
|
||||
"baseUrl": "kubernetes://unidesk/services/g14-provider-egress-proxy:18789",
|
||||
"healthPath": "/__unidesk/egress-proxy/health",
|
||||
"healthMode": "pod-ready"
|
||||
}
|
||||
],
|
||||
"requireAllInstancesHealthy": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "unidesk.ai/k3s/v1",
|
||||
"kind": "ManagedKubernetesService",
|
||||
"metadata": {
|
||||
"name": "g14-tcp-egress-gateway",
|
||||
"namespace": "unidesk"
|
||||
},
|
||||
"spec": {
|
||||
"adapterServiceId": "k3sctl-adapter-g14",
|
||||
"controlPlane": {
|
||||
"type": "kubernetes",
|
||||
"cluster": "unidesk-k3s",
|
||||
"context": "unidesk-k3s"
|
||||
},
|
||||
"route": {
|
||||
"kind": "kubernetes-service",
|
||||
"serviceName": "g14-tcp-egress-gateway",
|
||||
"servicePort": 18080,
|
||||
"deploymentName": "g14-tcp-egress-gateway"
|
||||
},
|
||||
"activeInstanceId": "G14-tcp-egress",
|
||||
"singleWriter": false,
|
||||
"expectedNodeIds": [
|
||||
"G14"
|
||||
],
|
||||
"instances": [
|
||||
{
|
||||
"id": "G14-tcp-egress",
|
||||
"nodeId": "G14",
|
||||
"role": "primary",
|
||||
"baseUrl": "kubernetes://unidesk/services/g14-tcp-egress-gateway:18080",
|
||||
"healthPath": "/health",
|
||||
"healthMode": "service-proxy"
|
||||
}
|
||||
],
|
||||
"requireAllInstancesHealthy": false
|
||||
}
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user