fix: route D601 PK01 postgres through master relay (#967)

Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
Lyon
2026-06-26 12:22:30 +08:00
committed by GitHub
parent 9d6ef37fb0
commit 754789c43c
6 changed files with 209 additions and 7 deletions
+24
View File
@@ -475,6 +475,30 @@ services:
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"
logging: *unidesk-log-rotation logging: *unidesk-log-rotation
pk01-postgres-relay:
image: unidesk_provider-gateway
container_name: unidesk-pk01-postgres-relay
restart: unless-stopped
network_mode: "host"
environment:
PK01_POSTGRES_RELAY_LISTEN_HOST: "${UNIDESK_PK01_POSTGRES_RELAY_LISTEN_HOST:-0.0.0.0}"
PK01_POSTGRES_RELAY_LISTEN_PORT: "${UNIDESK_PK01_POSTGRES_RELAY_LISTEN_PORT:-15433}"
PK01_POSTGRES_RELAY_TARGET_HOST: "${UNIDESK_PK01_POSTGRES_RELAY_TARGET_HOST:-82.156.23.220}"
PK01_POSTGRES_RELAY_TARGET_PORT: "${UNIDESK_PK01_POSTGRES_RELAY_TARGET_PORT:-5432}"
PK01_POSTGRES_RELAY_IDLE_TIMEOUT_MS: "${UNIDESK_PK01_POSTGRES_RELAY_IDLE_TIMEOUT_MS:-300000}"
PK01_POSTGRES_RELAY_ALLOWED_SOURCE_CIDRS: "${UNIDESK_PK01_POSTGRES_RELAY_ALLOWED_SOURCE_CIDRS:-127.0.0.1/32,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}"
LOG_FILE: "/var/log/unidesk/${UNIDESK_LOG_DAY:-manual}/${UNIDESK_LOG_PREFIX:-unidesk}_pk01-postgres-relay.jsonl"
command: ["bun", "/workspace/scripts/runtime/pk01-postgres-relay.js"]
volumes:
- .:/workspace:ro
- ${UNIDESK_LOG_DIR:-./.state/logs}:/var/log/unidesk
healthcheck:
test: ["CMD", "bun", "/workspace/scripts/runtime/pk01-postgres-relay.js", "--healthcheck"]
interval: 10s
timeout: 3s
retries: 6
logging: *unidesk-log-rotation
volumes: volumes:
unidesk_pgdata_10gb: unidesk_pgdata_10gb:
name: unidesk_pgdata_10gb name: unidesk_pgdata_10gb
+2 -2
View File
@@ -1,6 +1,6 @@
import { readConfig } from "./src/config"; import { readConfig } from "./src/config";
import { debugDispatch, debugHealth, debugSshPool, debugTask, isDebugDispatchCommand, type DebugDispatchCommand } from "./src/debug"; import { debugDispatch, debugHealth, debugSshPool, debugTask, isDebugDispatchCommand, type DebugDispatchCommand } from "./src/debug";
import { isRebuildableService, rebuildService, restartService, stackLogs, stackStatus, startStack, stopStack, unsupportedRebuildService, unsupportedRestartService } from "./src/docker"; import { isRebuildableService, isRestartableService, rebuildService, restartService, stackLogs, stackStatus, startStack, stopStack, unsupportedRebuildService, unsupportedRestartService } from "./src/docker";
import { emitError, emitJson, emitText, isRenderedCliResult } from "./src/output"; import { emitError, emitJson, emitText, isRenderedCliResult } from "./src/output";
import { cancelJob, jobWithTail, listJobs, listJobsSummary, readJob, renderJobStatusSummary, runJob } from "./src/jobs"; import { cancelJob, jobWithTail, listJobs, listJobsSummary, readJob, renderJobStatusSummary, runJob } from "./src/jobs";
import { checkHelp, parseCheckOptions, runChecks, runRecoveryGuardrailsCheck } from "./src/check"; import { checkHelp, parseCheckOptions, runChecks, runRecoveryGuardrailsCheck } from "./src/check";
@@ -488,7 +488,7 @@ async function main(): Promise<void> {
return; return;
} }
if (sub === "restart") { if (sub === "restart") {
if (!isRebuildableService(third)) { if (!isRestartableService(third)) {
const result = unsupportedRestartService(third); const result = unsupportedRestartService(third);
emitJson(commandName, result, false); emitJson(commandName, result, false);
process.exitCode = 1; process.exitCode = 1;
+147
View File
@@ -0,0 +1,147 @@
const net = require("node:net");
const fs = require("node:fs");
function readInt(name, fallback) {
const raw = process.env[name];
if (!raw) return fallback;
const parsed = Number(raw);
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
throw new Error(`${name} must be a TCP port number`);
}
return parsed;
}
const listenHost = process.env.PK01_POSTGRES_RELAY_LISTEN_HOST || "0.0.0.0";
const listenPort = readInt("PK01_POSTGRES_RELAY_LISTEN_PORT", 15433);
const targetHost = process.env.PK01_POSTGRES_RELAY_TARGET_HOST || "82.156.23.220";
const targetPort = readInt("PK01_POSTGRES_RELAY_TARGET_PORT", 5432);
const idleTimeoutMs = Number(process.env.PK01_POSTGRES_RELAY_IDLE_TIMEOUT_MS || 300000);
const allowedSourceCidrs = (process.env.PK01_POSTGRES_RELAY_ALLOWED_SOURCE_CIDRS || "127.0.0.1/32,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16")
.split(",")
.map((item) => item.trim())
.filter(Boolean);
const logFile = process.env.LOG_FILE || "";
function log(level, message, data = {}) {
const record = JSON.stringify({
ts: new Date().toISOString(),
service: "pk01-postgres-relay",
level,
message,
data,
});
console.log(record);
if (logFile) {
fs.appendFile(logFile, `${record}\n`, () => {});
}
}
function exitWithError(error) {
log("error", "fatal", { error: error instanceof Error ? error.message : String(error) });
process.exit(1);
}
function ipv4ToInt(value) {
const parts = value.split(".");
if (parts.length !== 4) return null;
let result = 0;
for (const part of parts) {
if (!/^\d+$/u.test(part)) return null;
const octet = Number(part);
if (!Number.isInteger(octet) || octet < 0 || octet > 255) return null;
result = ((result << 8) | octet) >>> 0;
}
return result >>> 0;
}
function normalizeRemoteAddress(value) {
if (!value) return "";
if (value.startsWith("::ffff:")) return value.slice("::ffff:".length);
return value;
}
function sourceAllowed(remoteAddress) {
const normalized = normalizeRemoteAddress(remoteAddress);
if (normalized === "::1") return true;
const ip = ipv4ToInt(normalized);
if (ip === null) return false;
return allowedSourceCidrs.some((cidr) => {
const [baseRaw, prefixRaw = "32"] = cidr.split("/");
const base = ipv4ToInt(baseRaw);
const prefix = Number(prefixRaw);
if (base === null || !Number.isInteger(prefix) || prefix < 0 || prefix > 32) return false;
const mask = prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0;
return (ip & mask) === (base & mask);
});
}
async function runHealthcheck() {
await new Promise((resolve, reject) => {
const socket = net.connect({ host: "127.0.0.1", port: listenPort }, resolve);
socket.setTimeout(2000);
socket.on("timeout", () => reject(new Error("healthcheck timeout")));
socket.on("error", reject);
socket.on("connect", () => socket.destroy());
});
}
if (process.argv.includes("--healthcheck")) {
runHealthcheck().then(() => process.exit(0), exitWithError);
} else {
let nextConnectionId = 0;
const server = net.createServer((client) => {
const connectionId = ++nextConnectionId;
const remoteAddress = normalizeRemoteAddress(client.remoteAddress || "");
if (!sourceAllowed(remoteAddress)) {
log("warn", "source_rejected", { connectionId, remoteAddress, allowedSourceCidrs });
client.destroy();
return;
}
const upstream = net.connect({ host: targetHost, port: targetPort });
client.setNoDelay(true);
upstream.setNoDelay(true);
client.setTimeout(idleTimeoutMs);
upstream.setTimeout(idleTimeoutMs);
let closed = false;
const closeBoth = (reason) => {
if (closed) return;
closed = true;
client.destroy();
upstream.destroy();
log("info", "closed", { connectionId, reason });
};
upstream.on("connect", () => {
log("info", "connected", { connectionId, target: `${targetHost}:${targetPort}` });
});
client.on("timeout", () => closeBoth("client_idle_timeout"));
upstream.on("timeout", () => closeBoth("upstream_idle_timeout"));
client.on("error", (error) => closeBoth(`client_error:${error.message}`));
upstream.on("error", (error) => closeBoth(`upstream_error:${error.message}`));
client.on("close", () => closeBoth("client_closed"));
upstream.on("close", () => closeBoth("upstream_closed"));
client.pipe(upstream);
upstream.pipe(client);
});
server.on("error", exitWithError);
server.listen(listenPort, listenHost, () => {
log("info", "listening", {
listen: `${listenHost}:${listenPort}`,
target: `${targetHost}:${targetPort}`,
networkMode: "host",
allowedSourceCidrs,
});
});
process.on("SIGTERM", () => {
log("info", "shutdown_requested", { signal: "SIGTERM" });
server.close(() => process.exit(0));
});
process.on("SIGINT", () => {
log("info", "shutdown_requested", { signal: "SIGINT" });
server.close(() => process.exit(0));
});
}
+33 -2
View File
@@ -44,12 +44,18 @@ export interface DeprecatedHostComposeEntry {
} }
const rebuildableServices = ["backend-core", "frontend", "dev-frontend-proxy", "provider-gateway", "todo-note", "project-manager", "baidu-netdisk", "oa-event-flow", "code-queue-mgr"] 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;
const restartableServices = [...rebuildableServices, "pk01-postgres-relay"] as const;
export type RebuildableService = typeof rebuildableServices[number]; export type RebuildableService = typeof rebuildableServices[number];
export type RestartableService = typeof restartableServices[number];
export function isRebuildableService(value: string | undefined): value is RebuildableService { export function isRebuildableService(value: string | undefined): value is RebuildableService {
return rebuildableServices.some((service) => service === value); return rebuildableServices.some((service) => service === value);
} }
export function isRestartableService(value: string | undefined): value is RestartableService {
return restartableServices.some((service) => service === value);
}
export function unsupportedRebuildService(value: string | undefined): Record<string, unknown> { export function unsupportedRebuildService(value: string | undefined): Record<string, unknown> {
const service = value ?? null; const service = value ?? null;
const classifications: Record<string, Record<string, unknown>> = { const classifications: Record<string, Record<string, unknown>> = {
@@ -83,6 +89,12 @@ export function unsupportedRebuildService(value: string | undefined): Record<str
replacement: "keep direct bridge repair and artifact plan/dry-run; live prod apply requires supervisor confirmation", replacement: "keep direct bridge repair and artifact plan/dry-run; live prod apply requires supervisor confirmation",
deleteAllowedLater: false, deleteAllowedLater: false,
}, },
"pk01-postgres-relay": {
classification: "image-reuse",
reason: "pk01-postgres-relay reuses the reviewed provider-gateway Bun image and has no source build step of its own",
replacement: "server restart pk01-postgres-relay",
deleteAllowedLater: false,
},
}; };
return { return {
ok: false, ok: false,
@@ -107,6 +119,7 @@ export function unsupportedRestartService(value: string | undefined): Record<str
error: "unsupported-server-restart", error: "unsupported-server-restart",
reason: typeof base.reason === "string" ? base.reason.replace(/server rebuild/g, "server restart") : base.reason, reason: typeof base.reason === "string" ? base.reason.replace(/server rebuild/g, "server restart") : base.reason,
replacement: typeof base.replacement === "string" ? base.replacement.replace(/server rebuild/g, "server restart") : base.replacement, replacement: typeof base.replacement === "string" ? base.replacement.replace(/server rebuild/g, "server restart") : base.replacement,
allowedServices: [...restartableServices],
policy: "server restart is a no-build maintenance entrypoint and must not silently mutate upstream images, D601 services, database, or unknown objects", policy: "server restart is a no-build maintenance entrypoint and must not silently mutate upstream images, D601 services, database, or unknown objects",
}; };
} }
@@ -307,6 +320,12 @@ export function writeComposeEnv(config: UniDeskConfig, freshLogPrefix: boolean):
UNIDESK_OA_EVENT_FLOW_BASE_URL: runtimeSecret("UNIDESK_OA_EVENT_FLOW_BASE_URL") || "http://oa-event-flow:4255", 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_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"), UNIDESK_OA_EVENT_FLOW_BIND_HOST: runtimeSecretWithDefault("UNIDESK_OA_EVENT_FLOW_BIND_HOST", restrictedHostBind, "127.0.0.1"),
UNIDESK_PK01_POSTGRES_RELAY_LISTEN_HOST: runtimeSecret("UNIDESK_PK01_POSTGRES_RELAY_LISTEN_HOST") || "0.0.0.0",
UNIDESK_PK01_POSTGRES_RELAY_LISTEN_PORT: runtimeSecret("UNIDESK_PK01_POSTGRES_RELAY_LISTEN_PORT") || "15433",
UNIDESK_PK01_POSTGRES_RELAY_TARGET_HOST: runtimeSecret("UNIDESK_PK01_POSTGRES_RELAY_TARGET_HOST") || "82.156.23.220",
UNIDESK_PK01_POSTGRES_RELAY_TARGET_PORT: runtimeSecret("UNIDESK_PK01_POSTGRES_RELAY_TARGET_PORT") || "5432",
UNIDESK_PK01_POSTGRES_RELAY_IDLE_TIMEOUT_MS: runtimeSecret("UNIDESK_PK01_POSTGRES_RELAY_IDLE_TIMEOUT_MS") || "300000",
UNIDESK_PK01_POSTGRES_RELAY_ALLOWED_SOURCE_CIDRS: runtimeSecret("UNIDESK_PK01_POSTGRES_RELAY_ALLOWED_SOURCE_CIDRS") || "127.0.0.1/32,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16",
UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_ENABLED: runtimeSecret("UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_ENABLED") || "true", 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://claudeqq.unidesk.svc.cluster.local:3290", UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_BASE_URL: runtimeSecret("UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_BASE_URL") || "http://claudeqq.unidesk.svc.cluster.local:3290",
UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_TARGET_TYPE: runtimeSecret("UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_TARGET_TYPE") || "private", UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_TARGET_TYPE: runtimeSecret("UNIDESK_CODE_QUEUE_NOTIFY_CLAUDEQQ_TARGET_TYPE") || "private",
@@ -442,7 +461,7 @@ export function rebuildService(config: UniDeskConfig, service: RebuildableServic
}; };
} }
export function restartService(config: UniDeskConfig, service: RebuildableService): unknown { export function restartService(config: UniDeskConfig, service: RestartableService): unknown {
const runtimeEnv = writeComposeEnv(config, false); const runtimeEnv = writeComposeEnv(config, false);
const compose = resolveComposeCommand(config, runtimeEnv.envFile); const compose = resolveComposeCommand(config, runtimeEnv.envFile);
const listServiceContainersCommand = [ const listServiceContainersCommand = [
@@ -650,6 +669,8 @@ export async function stackStatus(config: UniDeskConfig): Promise<unknown> {
const databaseBindHost = runtimeValue("UNIDESK_DATABASE_BIND_HOST") || "127.0.0.1"; 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 oaEventFlowBindHost = runtimeValue("UNIDESK_OA_EVENT_FLOW_BIND_HOST") || "127.0.0.1";
const oaEventFlowPort = Number(runtimeValue("UNIDESK_OA_EVENT_FLOW_PORT") || "4255"); const oaEventFlowPort = Number(runtimeValue("UNIDESK_OA_EVENT_FLOW_PORT") || "4255");
const pk01PostgresRelayListenHost = runtimeValue("UNIDESK_PK01_POSTGRES_RELAY_LISTEN_HOST") || "0.0.0.0";
const pk01PostgresRelayPort = Number(runtimeValue("UNIDESK_PK01_POSTGRES_RELAY_LISTEN_PORT") || "15433");
const coreHealth = dockerExecJson("unidesk-backend-core", "/health"); const coreHealth = dockerExecJson("unidesk-backend-core", "/health");
const overview = dockerExecJson("unidesk-backend-core", "/api/overview"); const overview = dockerExecJson("unidesk-backend-core", "/api/overview");
return { return {
@@ -676,6 +697,14 @@ export async function stackStatus(config: UniDeskConfig): Promise<unknown> {
listening: isPortListening(oaEventFlowPort), listening: isPortListening(oaEventFlowPort),
expected: oaEventFlowBindHost === "127.0.0.1" ? "local-only" : "restricted-to-code-queue-provider", expected: oaEventFlowBindHost === "127.0.0.1" ? "local-only" : "restricted-to-code-queue-provider",
}, },
{
name: "pk01-postgres-relay",
bindHost: pk01PostgresRelayListenHost,
hostPort: pk01PostgresRelayPort,
containerPort: pk01PostgresRelayPort,
listening: isPortListening(pk01PostgresRelayPort),
expected: "source-acl-private-or-local-only",
},
], ],
internalPorts: [ internalPorts: [
{ name: "backend-core", containerPort: config.network.core.containerPort, hostPort: null }, { name: "backend-core", containerPort: config.network.core.containerPort, hostPort: null },
@@ -684,6 +713,7 @@ export async function stackStatus(config: UniDeskConfig): Promise<unknown> {
{ name: "project-manager", containerPort: 4233, hostPort: null }, { name: "project-manager", containerPort: 4233, hostPort: null },
{ name: "baidu-netdisk", containerPort: 4244, hostPort: null }, { name: "baidu-netdisk", containerPort: 4244, hostPort: null },
{ name: "oa-event-flow", containerPort: 4255, hostPort: null }, { name: "oa-event-flow", containerPort: 4255, hostPort: null },
{ name: "pk01-postgres-relay", containerPort: pk01PostgresRelayPort, hostPort: pk01PostgresRelayPort },
], ],
containers, containers,
health: { health: {
@@ -693,6 +723,7 @@ export async function stackStatus(config: UniDeskConfig): Promise<unknown> {
providerIngress: await probe(`http://127.0.0.1:${config.network.providerIngress.port}/health`), providerIngress: await probe(`http://127.0.0.1:${config.network.providerIngress.port}/health`),
providerDataPortListening: isPortListening(config.network.providerData.port), providerDataPortListening: isPortListening(config.network.providerData.port),
database: dockerExec(config, "unidesk-database", ["pg_isready", "-U", config.database.user, "-d", config.database.name]), database: dockerExec(config, "unidesk-database", ["pg_isready", "-U", config.database.user, "-d", config.database.name]),
pk01PostgresRelay: dockerExec(config, "unidesk-pk01-postgres-relay", ["bun", "/workspace/scripts/runtime/pk01-postgres-relay.js", "--healthcheck"]),
overview, overview,
}, },
urls: { urls: {
@@ -781,7 +812,7 @@ export function stackLogs(config: UniDeskConfig, tailBytes: number): unknown {
const truncated = sizeBytes > tailBytes; const truncated = sizeBytes > tailBytes;
return { path, name: basename(path), sizeBytes, tailBytes, truncated, tail: tailFile(path, tailBytes) }; return { path, name: basename(path), sizeBytes, tailBytes, truncated, tail: tailFile(path, tailBytes) };
}); });
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 containerNames = ["unidesk-database", "unidesk-backend-core", "unidesk-frontend", "unidesk-dev-frontend-proxy", "unidesk-provider-gateway-main", "unidesk-pk01-postgres-relay", "todo-note-backend", "project-manager-backend", "baidu-netdisk-backend", "oa-event-flow-backend"];
const docker = containerNames.map((name) => { const docker = containerNames.map((name) => {
const result = runCommand(["docker", "logs", "--tail", "40", name], repoRoot); const result = runCommand(["docker", "logs", "--tail", "40", name], repoRoot);
return { return {
+2 -2
View File
@@ -20,7 +20,7 @@ export function rootHelp(): unknown {
{ command: "server cleanup plan [--min-age-hours N] [--limit N]", description: "Dry-run Docker image cleanup plan only: list active/protected images, stale candidates older than the default 24h threshold, risk, estimated reclaim, and manual review commands without deleting anything." }, { command: "server cleanup plan [--min-age-hours N] [--limit N]", description: "Dry-run Docker image cleanup plan only: list active/protected images, stale candidates older than the default 24h threshold, risk, estimated reclaim, and manual review commands without deleting anything." },
{ command: "gc plan|run|db-trace|policy|remote [--confirm] [--logs-keep-days N] [--include-browser-cache]", description: "One-time main-server or remote provider disk relief and low-risk anti-bloat policy for logs, journald, allowlisted /tmp artifacts, scoped core dumps and explicit trace telemetry retention; plan is read-only and run requires --confirm." }, { command: "gc plan|run|db-trace|policy|remote [--confirm] [--logs-keep-days N] [--include-browser-cache]", description: "One-time main-server or remote provider disk relief and low-risk anti-bloat policy for logs, journald, allowlisted /tmp artifacts, scoped core dumps and explicit trace telemetry retention; plan is read-only and run requires --confirm." },
{ command: "server rebuild <backend-core|frontend|dev-frontend-proxy|provider-gateway|todo-note|code-queue-mgr|project-manager|baidu-netdisk|oa-event-flow>", description: "Maintenance-only local Compose rebuild for reviewed main-server services; frontend standard release must use CI artifact plus deploy apply dev/prod artifact consumers." }, { command: "server rebuild <backend-core|frontend|dev-frontend-proxy|provider-gateway|todo-note|code-queue-mgr|project-manager|baidu-netdisk|oa-event-flow>", description: "Maintenance-only local Compose rebuild for reviewed main-server services; frontend standard release must use CI artifact plus deploy apply dev/prod artifact consumers." },
{ command: "server restart <backend-core|frontend|dev-frontend-proxy|provider-gateway|todo-note|code-queue-mgr|project-manager|baidu-netdisk|oa-event-flow>", description: "No-build single-service Compose restart for reviewed main-server maintenance recovery; returns an async job and validates the recreated container." }, { command: "server restart <backend-core|frontend|dev-frontend-proxy|provider-gateway|pk01-postgres-relay|todo-note|code-queue-mgr|project-manager|baidu-netdisk|oa-event-flow>", description: "No-build single-service Compose restart for reviewed main-server maintenance recovery; returns an async job and validates the recreated container." },
{ command: "provider attach <providerId> [--master-server URL] [--up] [--force] | provider triage <providerId> [--observed-error text] [--observed-scope scope] [--microservice id ...] [--full|--raw]", description: "Generate the minimal external provider-gateway env/compose bundle or run the low-noise read-only provider health triage contract." }, { command: "provider attach <providerId> [--master-server URL] [--up] [--force] | provider triage <providerId> [--observed-error text] [--observed-scope scope] [--microservice id ...] [--full|--raw]", description: "Generate the minimal external provider-gateway env/compose bundle or run the low-noise read-only provider health triage contract." },
{ command: "trans <route> [operation args...] (alias of ssh <route> ...)", description: "Open a Host SSH / WSL SSH maintenance session; provider WebSocket carries control and host.ssh.tcp-pool carries stdin/stdout/stderr data." }, { command: "trans <route> [operation args...] (alias of ssh <route> ...)", description: "Open a Host SSH / WSL SSH maintenance session; provider WebSocket carries control and host.ssh.tcp-pool carries stdin/stdout/stderr data." },
{ command: "trans gh:/owner/repo[/pr|/issue][/number[/1]] ls|cat|rg|apply-patch", description: "Treat GitHub PRs/issues as virtual text directories; `issue ls --search/--state` covers filtered reads, `cat|rg` reads first-floor body text, and `apply-patch` updates `body.md` through UniDesk gh plus apply-patch v2." }, { command: "trans gh:/owner/repo[/pr|/issue][/number[/1]] ls|cat|rg|apply-patch", description: "Treat GitHub PRs/issues as virtual text directories; `issue ls --search/--state` covers filtered reads, `cat|rg` reads first-floor body text, and `apply-patch` updates `body.md` through UniDesk gh plus apply-patch v2." },
@@ -118,7 +118,7 @@ export function serverHelp(action: string | undefined = undefined): unknown {
logs: "bun scripts/cli.ts server logs [--tail-bytes N]", logs: "bun scripts/cli.ts server logs [--tail-bytes N]",
cleanup: "bun scripts/cli.ts server cleanup plan [--min-age-hours N] [--limit N]", cleanup: "bun scripts/cli.ts server cleanup plan [--min-age-hours N] [--limit N]",
rebuild: "bun scripts/cli.ts server rebuild <backend-core|frontend|dev-frontend-proxy|provider-gateway|todo-note|code-queue-mgr|project-manager|baidu-netdisk|oa-event-flow>", rebuild: "bun scripts/cli.ts server rebuild <backend-core|frontend|dev-frontend-proxy|provider-gateway|todo-note|code-queue-mgr|project-manager|baidu-netdisk|oa-event-flow>",
restart: "bun scripts/cli.ts server restart <backend-core|frontend|dev-frontend-proxy|provider-gateway|todo-note|code-queue-mgr|project-manager|baidu-netdisk|oa-event-flow>", restart: "bun scripts/cli.ts server restart <backend-core|frontend|dev-frontend-proxy|provider-gateway|pk01-postgres-relay|todo-note|code-queue-mgr|project-manager|baidu-netdisk|oa-event-flow>",
}, },
cleanupPlan: { cleanupPlan: {
dryRunOnly: true, dryRunOnly: true,
@@ -919,7 +919,7 @@ spec:
- name: TCP_EGRESS_HTTP_PROXY - name: TCP_EGRESS_HTTP_PROXY
value: "http://d601-provider-egress-proxy.unidesk.svc.cluster.local:18789" value: "http://d601-provider-egress-proxy.unidesk.svc.cluster.local:18789"
- name: TCP_EGRESS_ROUTES - name: TCP_EGRESS_ROUTES
value: "postgres=15432=74.48.78.17:15432,pk01-postgres=25432=82.156.23.220:5432,oa-event-flow=4255=74.48.78.17:4255" value: "postgres=15432=74.48.78.17:15432,pk01-postgres=25432=74.48.78.17:15433,oa-event-flow=4255=74.48.78.17:4255"
- name: TCP_EGRESS_HEALTH_PORT - name: TCP_EGRESS_HEALTH_PORT
value: "18080" value: "18080"
- name: TCP_EGRESS_CONNECT_ATTEMPTS - name: TCP_EGRESS_CONNECT_ATTEMPTS