fix: route D601 PK01 postgres through master relay (#967)
Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user