fix: use owner-level unidesk state
This commit is contained in:
+2
-2
@@ -1,5 +1,5 @@
|
||||
.state/
|
||||
logs/
|
||||
.state
|
||||
logs
|
||||
node_modules/
|
||||
package-lock.json
|
||||
npm-debug.log*
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Non-secret local runtime hints copied by `bun scripts/cli.ts dev-env worktree add`.
|
||||
# Canonical credentials live under /root/.unidesk/.env and are never copied.
|
||||
# Canonical credentials and mutable state live under /root/.unidesk/.env and /root/.unidesk/.state; neither is copied.
|
||||
config/*.local.yaml
|
||||
config/*.local.yml
|
||||
config/*.local.json
|
||||
|
||||
+3
-3
@@ -78,7 +78,7 @@
|
||||
"hostProjectRoot": "/root/unidesk",
|
||||
"workspacePath": "/workspace",
|
||||
"composeFile": "docker-compose.yml",
|
||||
"composeEnvFile": ".state/docker-compose.env",
|
||||
"composeEnvFile": "/root/.unidesk/.state/docker-compose.env",
|
||||
"composeProject": "unidesk",
|
||||
"service": "provider-gateway",
|
||||
"runnerImage": "unidesk_provider-gateway"
|
||||
@@ -987,8 +987,8 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"stateDir": ".state",
|
||||
"logsDir": "logs",
|
||||
"stateDir": "/root/.unidesk/.state",
|
||||
"logsDir": "/root/.unidesk/.state/logs",
|
||||
"docsReferenceDir": "docs/reference"
|
||||
},
|
||||
"sshForwarding": {
|
||||
|
||||
@@ -398,7 +398,7 @@ controlPlane:
|
||||
key: GH_TOKEN
|
||||
unideskSshToken:
|
||||
id: tool-unidesk-ssh-token
|
||||
sourceRef: /root/unidesk/.state/docker-compose.env
|
||||
sourceRef: /root/.unidesk/.state/docker-compose.env
|
||||
sourceKey: UNIDESK_SSH_CLIENT_TOKEN
|
||||
targetRef:
|
||||
namespace: agentrun-v02
|
||||
|
||||
@@ -11,7 +11,7 @@ defaults:
|
||||
identityId: github.com
|
||||
|
||||
sources:
|
||||
root: /root/unidesk/.state/secrets
|
||||
root: /root/.unidesk/.state/secrets
|
||||
|
||||
identities:
|
||||
github.com:
|
||||
|
||||
@@ -8,7 +8,7 @@ metadata:
|
||||
- 2187
|
||||
|
||||
sources:
|
||||
root: /root/unidesk/.state/secrets
|
||||
root: /root/.unidesk/.state/secrets
|
||||
files:
|
||||
- sourceRef: hwlab/d601-v03-preset-users.env
|
||||
type: env
|
||||
|
||||
@@ -9,7 +9,7 @@ metadata:
|
||||
- 1237
|
||||
- 1236
|
||||
|
||||
sourceRoot: /root/unidesk/.state/secrets
|
||||
sourceRoot: /root/.unidesk/.state/secrets
|
||||
|
||||
targets:
|
||||
- id: d601-v03
|
||||
|
||||
@@ -116,7 +116,7 @@ postgres:
|
||||
|
||||
secrets:
|
||||
source: master-local
|
||||
root: /root/unidesk/.state/secrets
|
||||
root: /root/.unidesk/.state/secrets
|
||||
entries:
|
||||
- name: agentrun-nc01-v02-db-credentials
|
||||
sourceRef: platform-db/agentrun-nc01-v02-db.env
|
||||
|
||||
@@ -362,7 +362,7 @@ postgres:
|
||||
|
||||
secrets:
|
||||
source: master-local
|
||||
root: /root/unidesk/.state/secrets
|
||||
root: /root/.unidesk/.state/secrets
|
||||
entries:
|
||||
- name: sub2api-db-credentials
|
||||
sourceRef: platform-db/sub2api-db.env
|
||||
|
||||
@@ -286,7 +286,7 @@ app:
|
||||
publicExposure:
|
||||
enabled: true
|
||||
publicBaseUrl: https://gitea.pikapython.com
|
||||
secretRoot: /root/unidesk/.state/secrets
|
||||
secretRoot: /root/.unidesk/.state/secrets
|
||||
dns:
|
||||
hostname: gitea.pikapython.com
|
||||
expectedA: 82.156.23.220
|
||||
|
||||
@@ -18,7 +18,7 @@ server:
|
||||
serviceName: sub2api-master-egress-proxy
|
||||
containerName: unidesk-sub2api-master-egress-proxy
|
||||
image: ghcr.io/shadowsocks/ssserver-rust:latest
|
||||
configPath: /root/unidesk/.state/secrets/platform-infra/sub2api-master-egress-proxy.config.json
|
||||
configPath: /root/.unidesk/.state/secrets/platform-infra/sub2api-master-egress-proxy.config.json
|
||||
listenHost: 0.0.0.0
|
||||
listenPort: 18792
|
||||
health:
|
||||
@@ -95,11 +95,11 @@ templates:
|
||||
mode: trans-static-binary
|
||||
upstreamUrl: https://github.com/SagerNet/sing-box/releases/download/v1.13.14/sing-box-1.13.14-linux-amd64.tar.gz
|
||||
version: v1.13.14
|
||||
archiveCachePath: .state/artifacts/platform-infra/sing-box-1.13.14-linux-amd64.tar.gz
|
||||
archiveCachePath: /root/.unidesk/.state/artifacts/platform-infra/sing-box-1.13.14-linux-amd64.tar.gz
|
||||
archiveSha256: f48703461a15476951ac4967cdad339d986f4b8096b4eb3ff0829a500502d697
|
||||
archiveInstallPath: /var/cache/unidesk/host-egress-proxy/sing-box-1.13.14-linux-amd64.tar.gz
|
||||
binaryMember: sing-box-1.13.14-linux-amd64/sing-box
|
||||
binaryCachePath: .state/artifacts/platform-infra/sing-box-1.13.14-linux-amd64
|
||||
binaryCachePath: /root/.unidesk/.state/artifacts/platform-infra/sing-box-1.13.14-linux-amd64
|
||||
binarySha256: 68aeab83cc4ab2659a5b92232261a20746ccdafc3b3d1e19b2d63247eec3bbf7
|
||||
installPath: /usr/local/bin/sing-box
|
||||
configPath: /etc/unidesk/host-egress-proxy/sing-box.json
|
||||
|
||||
@@ -67,7 +67,7 @@ runtime:
|
||||
sslMode: require
|
||||
appDatabaseValue: langbot?ssl=require
|
||||
secrets:
|
||||
root: /root/unidesk/.state/secrets
|
||||
root: /root/.unidesk/.state/secrets
|
||||
appSourceRef: platform-infra/langbot.env
|
||||
storage:
|
||||
data:
|
||||
|
||||
@@ -67,7 +67,7 @@ runtime:
|
||||
sslMode: require
|
||||
sslRejectUnauthorized: false
|
||||
secrets:
|
||||
root: /root/unidesk/.state/secrets
|
||||
root: /root/.unidesk/.state/secrets
|
||||
appSourceRef: platform-infra/n8n.env
|
||||
encryptionKey: N8N_ENCRYPTION_KEY
|
||||
storage:
|
||||
|
||||
@@ -12,5 +12,5 @@ services:
|
||||
ports:
|
||||
- "0.0.0.0:18792:18792/tcp"
|
||||
volumes:
|
||||
- /root/unidesk/.state/secrets/platform-infra/sub2api-master-egress-proxy.config.json:/etc/shadowsocks-rust/config.json:ro
|
||||
- /root/.unidesk/.state/secrets/platform-infra/sub2api-master-egress-proxy.config.json:/etc/shadowsocks-rust/config.json:ro
|
||||
logging: *unidesk-log-rotation
|
||||
|
||||
@@ -441,7 +441,7 @@ runtime:
|
||||
sslMode: require
|
||||
pendingAllowed: true
|
||||
secrets:
|
||||
root: /root/unidesk/.state/secrets
|
||||
root: /root/.unidesk/.state/secrets
|
||||
appSourceRef: platform-infra/sub2api.env
|
||||
redis:
|
||||
serviceName: sub2api-redis
|
||||
|
||||
@@ -46,7 +46,7 @@ n8n:
|
||||
|
||||
archiveCallback:
|
||||
publicUrl: http://74.48.78.17:18081/webhooks/wechat-archive
|
||||
secretRoot: /root/unidesk/.state/secrets
|
||||
secretRoot: /root/.unidesk/.state/secrets
|
||||
tokenSourceRef: platform-infra/wechat-archive.env
|
||||
tokenKey: UNIDESK_WECHAT_ARCHIVE_TOKEN
|
||||
timeoutMs: 90000
|
||||
@@ -160,7 +160,7 @@ baiduNetdisk:
|
||||
proxyMode: backend-core-microservice-proxy
|
||||
configRef: config.json#microservices.baidu-netdisk
|
||||
staging:
|
||||
hostRoot: /root/unidesk/.state/baidu-netdisk/staging
|
||||
hostRoot: /root/.unidesk/.state/baidu-netdisk/staging
|
||||
containerRoot: /data/staging
|
||||
outboxDir: wechat-archive/outbox
|
||||
pullDir: wechat-archive/pulls
|
||||
|
||||
@@ -11,7 +11,7 @@ metadata:
|
||||
- 2256
|
||||
|
||||
sources:
|
||||
root: /root/unidesk/.state/secrets
|
||||
root: /root/.unidesk/.state/secrets
|
||||
files:
|
||||
- sourceRef: platform-db/langbot-db.env
|
||||
type: env
|
||||
|
||||
+15
-15
@@ -62,27 +62,27 @@ gc:
|
||||
enabled: false
|
||||
keepDays: 14
|
||||
fileRoots:
|
||||
e2e: .state/e2e
|
||||
validation: .state/validation
|
||||
jobs: .state/jobs
|
||||
codex-queue-output-archive: .state/codex-queue/output-archive
|
||||
e2e: /root/.unidesk/.state/e2e
|
||||
validation: /root/.unidesk/.state/validation
|
||||
jobs: /root/.unidesk/.state/jobs
|
||||
codex-queue-output-archive: /root/.unidesk/.state/codex-queue/output-archive
|
||||
dirRoots:
|
||||
deploy-exports: .state/deploy/exports
|
||||
deploy-resolve: .state/deploy/resolve
|
||||
deploy-exports: /root/.unidesk/.state/deploy/exports
|
||||
deploy-resolve: /root/.unidesk/.state/deploy/resolve
|
||||
stateStaleScratch:
|
||||
enabled: false
|
||||
keepHours: 24
|
||||
fileRoots:
|
||||
playwright-cli-screenshots: .state/playwright-cli/screenshots
|
||||
playwright-cli-sessions: .state/playwright-cli/sessions
|
||||
perf: .state/perf
|
||||
tmp: .state/tmp
|
||||
web-observe: .state/web-observe
|
||||
playwright-cli-screenshots: /root/.unidesk/.state/playwright-cli/screenshots
|
||||
playwright-cli-sessions: /root/.unidesk/.state/playwright-cli/sessions
|
||||
perf: /root/.unidesk/.state/perf
|
||||
tmp: /root/.unidesk/.state/tmp
|
||||
web-observe: /root/.unidesk/.state/web-observe
|
||||
dirRoots:
|
||||
hwlab-cd: .state/hwlab-cd
|
||||
codex-queue-stats-verify: .state/codex-queue-stats-verify
|
||||
codex-queue-perf: .state/codex-queue-perf
|
||||
tmp: .state/tmp
|
||||
hwlab-cd: /root/.unidesk/.state/hwlab-cd
|
||||
codex-queue-stats-verify: /root/.unidesk/.state/codex-queue-stats-verify
|
||||
codex-queue-perf: /root/.unidesk/.state/codex-queue-perf
|
||||
tmp: /root/.unidesk/.state/tmp
|
||||
legacyDockerImages:
|
||||
enabled: true
|
||||
minAgeHours: 12
|
||||
|
||||
@@ -108,7 +108,7 @@ services:
|
||||
logFile: /var/log/unidesk/decision-center.jsonl
|
||||
database:
|
||||
sourceRef:
|
||||
path: .state/secrets/decision-center/nc01-db.env
|
||||
path: /root/.unidesk/.state/secrets/decision-center/nc01-db.env
|
||||
key: DATABASE_URL
|
||||
secretName: decision-center-runtime-secrets
|
||||
secretKey: DATABASE_URL
|
||||
@@ -162,7 +162,7 @@ services:
|
||||
logFile: /var/log/unidesk/todo-note.jsonl
|
||||
database:
|
||||
sourceRef:
|
||||
path: .state/secrets/decision-center/nc01-db.env
|
||||
path: /root/.unidesk/.state/secrets/decision-center/nc01-db.env
|
||||
key: DATABASE_URL
|
||||
secretName: decision-center-runtime-secrets
|
||||
secretKey: DATABASE_URL
|
||||
@@ -240,7 +240,7 @@ runtimeAuth:
|
||||
|
||||
runtimeSecrets:
|
||||
sourceRef:
|
||||
path: .state/secrets/unidesk-host-k8s.env
|
||||
path: /root/.unidesk/.state/secrets/unidesk-host-k8s.env
|
||||
target:
|
||||
name: unidesk-runtime-secrets
|
||||
keys:
|
||||
|
||||
+4
-4
@@ -87,7 +87,7 @@ services:
|
||||
UNIDESK_LOG_RETENTION_BYTES: "${UNIDESK_LOG_RETENTION_BYTES:-512MiB}"
|
||||
volumes:
|
||||
- ${UNIDESK_LOG_DIR}:/var/log/unidesk
|
||||
- ./.state/baidu-netdisk/staging:/data/baidu-netdisk-staging
|
||||
- /root/.unidesk/.state/baidu-netdisk/staging:/data/baidu-netdisk-staging
|
||||
healthcheck:
|
||||
test:
|
||||
- "CMD-SHELL"
|
||||
@@ -171,7 +171,7 @@ services:
|
||||
UNIDESK_LOG_RETENTION_BYTES: "${UNIDESK_LOG_RETENTION_BYTES:-512MiB}"
|
||||
volumes:
|
||||
- ${UNIDESK_LOG_DIR}:/var/log/unidesk
|
||||
- ./.state/code-agent-sandbox:/var/lib/unidesk/code-agent-sandbox
|
||||
- /root/.unidesk/.state/code-agent-sandbox:/var/lib/unidesk/code-agent-sandbox
|
||||
healthcheck:
|
||||
test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:4260/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
|
||||
interval: 5s
|
||||
@@ -362,7 +362,7 @@ services:
|
||||
UNIDESK_DEPLOY_REQUESTED_COMMIT: "${UNIDESK_BAIDU_NETDISK_DEPLOY_REQUESTED_COMMIT:-}"
|
||||
volumes:
|
||||
- ${UNIDESK_LOG_DIR}:/var/log/unidesk
|
||||
- ./.state/baidu-netdisk/staging:/data/staging
|
||||
- /root/.unidesk/.state/baidu-netdisk/staging:/data/staging
|
||||
healthcheck:
|
||||
test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:4244/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
|
||||
interval: 5s
|
||||
@@ -493,7 +493,7 @@ services:
|
||||
command: ["bun", "/workspace/scripts/runtime/pk01-postgres-relay.js"]
|
||||
volumes:
|
||||
- .:/workspace:ro
|
||||
- ${UNIDESK_LOG_DIR:-./.state/logs}:/var/log/unidesk
|
||||
- ${UNIDESK_LOG_DIR:-/root/.unidesk/.state/logs}:/var/log/unidesk
|
||||
healthcheck:
|
||||
test: ["CMD", "bun", "/workspace/scripts/runtime/pk01-postgres-relay.js", "--healthcheck"]
|
||||
interval: 10s
|
||||
|
||||
@@ -54,9 +54,9 @@ If a manual repair is needed to unblock the platform, the durable fix must be co
|
||||
|
||||
## Worktree-Independent Local State
|
||||
|
||||
Tracked source, YAML and scripts belong to the selected checkout; operator credentials and mutable runtime state do not. UniDesk's canonical owner-level roots are `/root/.unidesk/.env` for credentials and `/root/.unidesk/.state` for mutable state, caches, generated artifacts and Secret source files. YAML `sourceRef`, CLI path configuration and source defaults that consume these materials must use those fixed absolute roots. They must not derive the path from `process.cwd()`, the module's task-worktree root, or a relative `.env`/`.state` path.
|
||||
Tracked source, YAML and scripts belong to the selected checkout; operator credentials and mutable runtime state do not. UniDesk's canonical owner-level roots are `/root/.unidesk/.env` for credentials and `/root/.unidesk/.state` for mutable state, logs, caches, generated artifacts and Secret source files. YAML `sourceRef`, CLI path configuration and source defaults that consume these materials must use those fixed absolute roots. They must not derive the path from `process.cwd()`, the module's task-worktree root, or a relative `.env`/`.state`/`logs` path.
|
||||
|
||||
`/root/unidesk/.env` and `/root/unidesk/.state` are compatibility symlinks to the canonical owner-level roots. They preserve old main-worktree entrypoints but are not source authority. `.worktreecopy` must not copy either tree, and task worktrees must not receive private copies or task-specific compatibility links. A command that fails only because a task worktree lacks `.env`, `.state`, `secrets`, cache or generated state has a path-contract defect: fix the owning config/helper to consume the canonical absolute path before continuing the original operation.
|
||||
`/root/unidesk/.env`, `/root/unidesk/.state` and `/root/unidesk/logs` are compatibility symlinks to the canonical owner-level roots. They preserve old main-worktree entrypoints but are not source authority. `.worktreecopy` must not copy these trees, and task worktrees must not receive private copies or task-specific compatibility links. A command that fails only because a task worktree lacks `.env`, `.state`, `logs`, `secrets`, cache or generated state has a path-contract defect: fix the owning config/helper to consume the canonical absolute path before continuing the original operation.
|
||||
|
||||
When diagnosing a missing worktree file, first classify ownership. A tracked file is repaired through remote/base synchronization; an ephemeral test fixture uses an explicit temporary directory; durable local credentials or state move to the canonical owner-level root and are referenced absolutely. Never repair the latter by copying secrets/state into every worktree, silently falling back to the current directory, embedding values in Git, or maintaining divergent per-worktree data. A migration must preserve existing contents, keep the canonical directories owner-only, verify the resolved symlink target and data size, and leave the main-repo compatibility symlink in place.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ NC01 is the current host registered as a UniDesk provider-gateway node. Host mai
|
||||
|
||||
NC01 databases run on host-native PostgreSQL, not Docker or Kubernetes. Kubernetes workloads that need PostgreSQL must use YAML-declared external PostgreSQL bridge objects and Secrets.
|
||||
|
||||
For HWLAB v0.3, the Kubernetes Service `nc01-host-postgres` in namespace `hwlab-v03` points to host PostgreSQL at `10.42.0.1:5432`. Runtime database Secrets are sourced from `.state/secrets/hwlab/*` via YAML sourceRef and must only be reported as present/fingerprint metadata, never as full values.
|
||||
For HWLAB v0.3, the Kubernetes Service `nc01-host-postgres` in namespace `hwlab-v03` points to host PostgreSQL at `10.42.0.1:5432`. Runtime database Secrets are sourced from `/root/.unidesk/.state/secrets/hwlab/*` via YAML sourceRef and must only be reported as present/fingerprint metadata, never as full values.
|
||||
|
||||
## Decision Center
|
||||
|
||||
|
||||
@@ -435,7 +435,7 @@ export function agentRunLaneSummary(spec: AgentRunLaneSpec): Record<string, unkn
|
||||
},
|
||||
secrets: spec.secrets.map((secret) => ({
|
||||
id: secret.id,
|
||||
sourceRef: secret.sourceMode === "codex-config" ? secret.sourceRef : secret.sourceRef.startsWith("/") ? secret.sourceRef : `.state/secrets/${secret.sourceRef}`,
|
||||
sourceRef: secret.sourceMode === "codex-config" ? secret.sourceRef : secret.sourceRef.startsWith("/") ? secret.sourceRef : rootPath(".state", "secrets", secret.sourceRef),
|
||||
sourceMode: secret.sourceMode,
|
||||
sourceKey: secret.sourceKey,
|
||||
sourceFormat: secret.sourceFormat,
|
||||
|
||||
@@ -739,7 +739,7 @@ export function readSecretSourceValue(spec: AgentRunLaneSpec, source: LaneSecret
|
||||
if (value.length === 0) throw new Error(`secret source ${sourceRef} is empty`);
|
||||
value = transformSecretSourceValue(spec, source, value);
|
||||
return {
|
||||
redactedPath: source.sourceMode === "codex-config" ? sourceRef : sourceRef.startsWith("/") ? redactAbsoluteSecretPath(sourceRef) : `.state/secrets/${sourceRef}`,
|
||||
redactedPath: source.sourceMode === "codex-config" ? sourceRef : redactAbsoluteSecretPath(sourcePath ?? sourceRef),
|
||||
value,
|
||||
valueBytes: Buffer.byteLength(value, "utf8"),
|
||||
fingerprint: sha256Fingerprint(value),
|
||||
|
||||
@@ -78,7 +78,7 @@ export function verifyLocalArtifactLabels(
|
||||
|
||||
export function devFrontendAuthPatchScript(config: UniDeskConfig): string {
|
||||
const sshClientToken = composeRuntimeEnvValue("UNIDESK_SSH_CLIENT_TOKEN");
|
||||
if (sshClientToken === null) throw new Error("UNIDESK_SSH_CLIENT_TOKEN must be present in .state/docker-compose.env before deploying dev frontend");
|
||||
if (sshClientToken === null) throw new Error(`UNIDESK_SSH_CLIENT_TOKEN must be present in ${rootPath(".state", "docker-compose.env")} before deploying dev frontend`);
|
||||
const sshClientRouteAllowlist = composeRuntimeEnvValue("UNIDESK_SSH_CLIENT_ROUTE_ALLOWLIST") ?? "G14,G14:*,D601,D601:*";
|
||||
const secretData = {
|
||||
AUTH_USERNAME: base64(config.auth.username),
|
||||
|
||||
@@ -38,7 +38,7 @@ export const baiduNetdiskRuntimeSecretRequirements: RuntimeSecretRequirement[] =
|
||||
|
||||
export const baiduNetdiskAuthHealthGate: AuthHealthGate = {
|
||||
requiredFields: ["configured", "clientIdConfigured", "clientSecretConfigured", "tokenKeyConfigured", "loggedIn"],
|
||||
recoveryHint: "Restore UNIDESK_BAIDU_NETDISK_CLIENT_ID, UNIDESK_BAIDU_NETDISK_CLIENT_SECRET, and UNIDESK_BAIDU_NETDISK_TOKEN_KEY in the canonical .state/docker-compose.env, recreate only baidu-netdisk with the canonical env file, then verify microservice health baidu-netdisk.",
|
||||
recoveryHint: "Restore UNIDESK_BAIDU_NETDISK_CLIENT_ID, UNIDESK_BAIDU_NETDISK_CLIENT_SECRET, and UNIDESK_BAIDU_NETDISK_TOKEN_KEY in /root/.unidesk/.state/docker-compose.env, recreate only baidu-netdisk with the canonical env file, then verify microservice health baidu-netdisk.",
|
||||
};
|
||||
|
||||
export const artifactConsumerSpecs: Record<string, ArtifactConsumerSpec> = {
|
||||
|
||||
@@ -98,7 +98,7 @@ export function ciHelp(): Record<string, unknown> {
|
||||
},
|
||||
install: {
|
||||
defaultMode: "async-job",
|
||||
waitFlag: "Use --wait only for explicit synchronous debugging; the default returns a .state/jobs job immediately with install-status follow-up.",
|
||||
waitFlag: "Use --wait only for explicit synchronous debugging; the default returns a /root/.unidesk/.state/jobs job immediately with install-status follow-up.",
|
||||
statusCommand: "bun scripts/cli.ts ci install-status <jobId|latest>",
|
||||
prewarmDefault: true,
|
||||
skipPrewarm: "Use --skip-prewarm only to refresh Tekton/CI manifests when runtime images are already present or prewarm is blocked by provider root/containerd permissions.",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { chromium, type BrowserContext, type Request } from "playwright";
|
||||
import { existsSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { homedir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { readConfig } from "./config";
|
||||
|
||||
interface CodeQueuePerfOptions {
|
||||
@@ -20,6 +21,8 @@ interface ApiTiming {
|
||||
durationMs: number;
|
||||
}
|
||||
|
||||
const isCheckMode = process.execArgv.includes("--check");
|
||||
|
||||
function argValue(args: string[], name: string): string | null {
|
||||
const index = args.indexOf(name);
|
||||
if (index === -1) return null;
|
||||
@@ -90,7 +93,7 @@ function parseNumberAttr(value: string | null): number | null {
|
||||
}
|
||||
|
||||
async function runCodeQueuePerf(options: CodeQueuePerfOptions): Promise<Record<string, unknown>> {
|
||||
const browsersPath = process.env.PLAYWRIGHT_BROWSERS_PATH || ".state/playwright-browsers";
|
||||
const browsersPath = process.env.PLAYWRIGHT_BROWSERS_PATH || join(homedir(), ".unidesk", ".state", "playwright-browsers");
|
||||
const fullChromePath = resolve(browsersPath, "chromium-1217/chrome-linux64/chrome");
|
||||
const launchArgs = [
|
||||
"--no-sandbox",
|
||||
@@ -211,11 +214,15 @@ async function runCodeQueuePerf(options: CodeQueuePerfOptions): Promise<Record<s
|
||||
}
|
||||
}
|
||||
|
||||
const options = readOptions();
|
||||
const result = await runCodeQueuePerf(options);
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(result));
|
||||
} else {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
async function main(): Promise<void> {
|
||||
const options = readOptions();
|
||||
const result = await runCodeQueuePerf(options);
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(result));
|
||||
} else {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
}
|
||||
if (result.ok !== true) process.exitCode = 1;
|
||||
}
|
||||
if (result.ok !== true) process.exitCode = 1;
|
||||
|
||||
if (import.meta.main && !isCheckMode) await main();
|
||||
|
||||
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { createInterface } from "node:readline";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { repoRoot } from "./config";
|
||||
import { rootPath } from "./config";
|
||||
import type { RenderedCliResult } from "./output";
|
||||
|
||||
type CodexTraceAction = "help" | "list" | "collect" | "show" | "grep" | "active";
|
||||
@@ -85,7 +85,7 @@ export function codexTraceHelp(): Record<string, unknown> {
|
||||
"bun scripts/cli.ts codex trace grep --session <id> --messages --pattern 'Playwright|web-probe'",
|
||||
"bun scripts/cli.ts codex trace grep --session <id> --failed-only [--tool exec_command]",
|
||||
"bun scripts/cli.ts codex trace grep --pattern 'playwright|auth-login-failed' [--file sessions/...jsonl] [--since ISO]",
|
||||
"bun scripts/cli.ts codex trace collect [--root ~/.codex] [--output .state/codex-trace/<timestamp>] [--limit 30]",
|
||||
"bun scripts/cli.ts codex trace collect [--root ~/.codex] [--output /root/.unidesk/.state/codex-trace/<timestamp>] [--limit 30]",
|
||||
"bun scripts/cli.ts codex trace show --session <id> [--root ~/.codex] [--tail-bytes 12000]",
|
||||
],
|
||||
safety: [
|
||||
@@ -97,7 +97,7 @@ export function codexTraceHelp(): Record<string, unknown> {
|
||||
],
|
||||
options: {
|
||||
"--root <dir>": "Codex home or another trace root. Defaults to ~/.codex.",
|
||||
"--output <dir>": "Collect destination. Defaults to .state/codex-trace/<timestamp>.",
|
||||
"--output <dir>": "Collect destination. Defaults to /root/.unidesk/.state/codex-trace/<timestamp>.",
|
||||
"--limit <n>": `Maximum included files, default ${defaultLimit}, max ${maxLimit}.`,
|
||||
"--max-depth <n>": `Recursive scan depth, default ${defaultMaxDepth}, max ${maxDepthLimit}.`,
|
||||
"--max-file-bytes <n|25MiB>": "Skip files larger than this during collect/list inclusion.",
|
||||
@@ -247,7 +247,7 @@ function codexTraceList(options: CodexTraceOptions, candidates: CodexTraceCandid
|
||||
|
||||
function codexTraceCollect(options: CodexTraceOptions, candidates: CodexTraceCandidate[]): Record<string, unknown> | RenderedCliResult {
|
||||
const selected = selectedCandidates(options, candidates);
|
||||
const outputDir = options.outputDir ?? path.join(repoRoot, ".state", "codex-trace", timestampForPath());
|
||||
const outputDir = options.outputDir ?? rootPath(".state", "codex-trace", timestampForPath());
|
||||
const copied: Record<string, unknown>[] = [];
|
||||
if (!options.dryRun) mkdirSync(outputDir, { recursive: true, mode: 0o700 });
|
||||
for (const candidate of selected) {
|
||||
|
||||
@@ -67,7 +67,7 @@ function processDiscoveryPlan(sessionId: string): Record<string, unknown> {
|
||||
sessionId,
|
||||
mutation: false,
|
||||
signals: [
|
||||
".state/commander/sessions/<sessionId>.json",
|
||||
"/root/.unidesk/.state/commander/sessions/<sessionId>.json",
|
||||
"host process table filtered by executable and cwd markers",
|
||||
"PTY/stdio bridge heartbeat file",
|
||||
"last prompt/trace event sequence",
|
||||
@@ -280,7 +280,7 @@ function healthEndpointValidation(): Record<string, unknown> {
|
||||
function stateFileValidation(sessionId: string): Record<string, unknown> {
|
||||
return {
|
||||
surface: "state file",
|
||||
storageRoot: ".state/commander/",
|
||||
storageRoot: "/root/.unidesk/.state/commander/",
|
||||
validationMethod: "write and read a session record only under a temporary directory during dry-run/manual inspection",
|
||||
files: [
|
||||
`sessions/${sessionId}.json`,
|
||||
@@ -294,7 +294,7 @@ function stateFileValidation(sessionId: string): Record<string, unknown> {
|
||||
"temporary state root is deleted after the smoke contract",
|
||||
],
|
||||
noRuntimeSideEffects: [
|
||||
"do not touch the live .state/commander directory",
|
||||
"do not touch the live /root/.unidesk/.state/commander directory",
|
||||
"do not patch database state",
|
||||
"do not discover or signal live host Codex processes",
|
||||
],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { homedir } from "node:os";
|
||||
import { dirname, isAbsolute, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export interface UniDeskConfig {
|
||||
@@ -101,8 +102,14 @@ export interface UniDeskMicroserviceConfig {
|
||||
|
||||
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
||||
export const repoRoot = join(moduleDir, "..", "..");
|
||||
export const stateRoot = join(homedir(), ".unidesk", ".state");
|
||||
|
||||
export function rootPath(...parts: string[]): string {
|
||||
const first = parts[0];
|
||||
if (first === undefined) return repoRoot;
|
||||
if (isAbsolute(first)) return join(first, ...parts.slice(1));
|
||||
if (first === ".state") return join(stateRoot, ...parts.slice(1));
|
||||
if (first.startsWith(".state/")) return join(stateRoot, first.slice(".state/".length), ...parts.slice(1));
|
||||
return join(repoRoot, ...parts);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ import { k8sKubeconfig, nativeK3sCtrAddress, nativeK3sImage, nativeK3sInstallVer
|
||||
|
||||
export function syncDevFrontendAuthScript(config: UniDeskConfig): string {
|
||||
const sshClientToken = composeRuntimeEnvValue("UNIDESK_SSH_CLIENT_TOKEN");
|
||||
if (sshClientToken === null) throw new Error("UNIDESK_SSH_CLIENT_TOKEN must be present in .state/docker-compose.env before deploying dev frontend");
|
||||
if (sshClientToken === null) throw new Error(`UNIDESK_SSH_CLIENT_TOKEN must be present in ${rootPath(".state", "docker-compose.env")} before deploying dev frontend`);
|
||||
const sshClientRouteAllowlist = composeRuntimeEnvValue("UNIDESK_SSH_CLIENT_ROUTE_ALLOWLIST") ?? "G14,G14:*,D601,D601:*";
|
||||
const data = {
|
||||
AUTH_USERNAME: Buffer.from(config.auth.username, "utf8").toString("base64"),
|
||||
|
||||
@@ -175,22 +175,17 @@ export function writeComposeEnv(config: UniDeskConfig, freshLogPrefix: boolean):
|
||||
if (previous.length > 0 && previous !== legacyDefault) return previous;
|
||||
return defaultValue;
|
||||
};
|
||||
let logRoot: string;
|
||||
const logRoot = resolve(rootPath(config.paths.logsDir));
|
||||
let logDay: string;
|
||||
let logPrefix: string;
|
||||
if (!freshLogPrefix && previousRaw.length > 0) {
|
||||
const previousLogDir = previousValue("UNIDESK_LOG_DIR");
|
||||
const previousLogRoot = previousLogDir && /^\d{8}$/u.test(basename(previousLogDir)) ? dirname(previousLogDir) : previousLogDir;
|
||||
logRoot = previousLogRoot || rootPath(config.paths.logsDir);
|
||||
logPrefix = previousValue("UNIDESK_LOG_PREFIX") || localDateParts(new Date()).stamp;
|
||||
logDay = previousValue("UNIDESK_LOG_DAY") || logPrefix.match(/^\d{8}/u)?.[0] || localDateParts(new Date()).day;
|
||||
} else {
|
||||
const parts = localDateParts(new Date());
|
||||
logRoot = resolve(rootPath(config.paths.logsDir));
|
||||
logDay = parts.day;
|
||||
logPrefix = parts.stamp;
|
||||
}
|
||||
logRoot = resolve(logRoot);
|
||||
mkdirSync(join(logRoot, logDay), { recursive: true });
|
||||
chmodSync(logRoot, 0o777);
|
||||
chmodSync(join(logRoot, logDay), 0o777);
|
||||
|
||||
+6
-5
@@ -2,7 +2,7 @@ import { spawnSync } from "node:child_process";
|
||||
import { closeSync, existsSync, ftruncateSync, lstatSync, mkdirSync, opendirSync, openSync, readdirSync, readFileSync, readSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
||||
import { basename, dirname, join, resolve } from "node:path";
|
||||
|
||||
import { type UniDeskConfig, repoRoot, rootPath } from "./config";
|
||||
import { type UniDeskConfig, repoRoot, rootPath, stateRoot } from "./config";
|
||||
import { runRemoteGcCommand } from "./gc-remote";
|
||||
|
||||
type GcRisk = "low" | "medium" | "high" | "blocked";
|
||||
@@ -928,11 +928,12 @@ function yamlStateRoots(value: unknown, label: string): GcPathRoot[] {
|
||||
return Object.entries(record).map(([id, rawPath]) => {
|
||||
if (!/^[a-z0-9._-]+$/iu.test(id)) throw new Error(`${label}.${id} must use a simple id`);
|
||||
const displayPath = yamlString(rawPath, `${label}.${id}`);
|
||||
if (displayPath.startsWith("/") || displayPath.includes("..") || displayPath.includes("\\")) {
|
||||
throw new Error(`${label}.${id} must be a repo-relative .state path without ..`);
|
||||
if (displayPath.includes("..") || displayPath.includes("\\")) {
|
||||
throw new Error(`${label}.${id} must be a canonical state path without ..`);
|
||||
}
|
||||
if (!displayPath.startsWith(".state/")) throw new Error(`${label}.${id} must start with .state/`);
|
||||
return { id, displayPath, path: rootPath(...displayPath.split("/")) };
|
||||
const path = resolvePath(displayPath);
|
||||
if (!path.startsWith(`${stateRoot}/`)) throw new Error(`${label}.${id} must be under ${stateRoot}/`);
|
||||
return { id, displayPath, path };
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -94,9 +94,9 @@ export function rootHelp(): unknown {
|
||||
{ command: "codex steer-confirm <taskId> --steer-id <id> [--raw]", description: "Read-only lookup for a steerId in task trace so deliveryUnconfirmed can be resolved without resending the corrective prompt." },
|
||||
{ command: "codex interrupt|cancel <taskId>", description: "Request interrupt for a running Code Queue task, or cancel a queued/retry_wait task, through the same private proxy." },
|
||||
{ command: "codex queues [--full|--all] [--limit N] [--page N|--offset N]", description: "Read legacy Code Queue archive summaries. Legacy queue create/merge and move are frozen; use agentrun create/apply/get/cancel for new work." },
|
||||
{ command: "job|jobs list [--limit N] [--include-command]", description: "List async jobs from .state/jobs with a bounded default page and progress summaries." },
|
||||
{ command: "job|jobs list [--limit N] [--include-command]", description: "List async jobs from /root/.unidesk/.state/jobs with a bounded default page and progress summaries." },
|
||||
{ command: "job status|get|read <jobId|latest> [--tail-bytes N]", description: "Show job state with a structured progress summary and bounded stdout/stderr tails." },
|
||||
{ command: "job cancel <jobId>", description: "Cancel a queued/running async job through the .state/jobs control entry and keep a terminal canceled record." },
|
||||
{ command: "job cancel <jobId>", description: "Cancel a queued/running async job through the /root/.unidesk/.state/jobs control entry and keep a terminal canceled record." },
|
||||
{ command: "debug health", description: "Probe internal core, nodes, system/Docker status, frontend, provider ingress, and public boundary." },
|
||||
{ command: "debug ssh-pool <providerId>", description: "Show bounded host.ssh.tcp-pool labels for one provider, including ready/claimed/desired/lastError." },
|
||||
{ command: "debug egress-proxy <providerId>", description: "Show provider-gateway egress proxy tunnel counts, stale tunnel diagnosis, active target summaries, and recent closed tunnel lifecycle without URL credential leakage." },
|
||||
@@ -358,7 +358,7 @@ function gcHelp(): unknown {
|
||||
"--limit N": "number of candidates returned and executed by run when --full is not set; default 50",
|
||||
"--result-limit N": "number of per-candidate run results returned when --full is not set; default 50",
|
||||
"--full|--raw": "return and run against all candidates rather than the default bounded page",
|
||||
"--include-browser-cache": "also remove repo-local .state/playwright-browsers cache",
|
||||
"--include-browser-cache": "also remove /root/.unidesk/.state/playwright-browsers cache",
|
||||
"--include-tool-caches": "local and remote explicit opt-in: remove rebuildable npm/npx/Bun package caches from fixed allowlisted paths",
|
||||
"--include-state-artifacts": "manual local gc only: opt in to stale UniDesk .state artifact retention for allowlisted diagnostic files and deploy artifact direct directories",
|
||||
"--state-artifact-keep-days N": "keep recent UniDesk .state artifacts for N days; default 14; must be a positive integer",
|
||||
@@ -428,7 +428,7 @@ function codexHelp(): unknown {
|
||||
"bun scripts/cli.ts codex trace grep --session <id> --messages --pattern 'Playwright|web-probe'",
|
||||
"bun scripts/cli.ts codex trace grep --session <id> --failed-only [--tool exec_command]",
|
||||
"bun scripts/cli.ts codex trace grep --pattern 'playwright|auth-login-failed' [--file sessions/...jsonl] [--since ISO]",
|
||||
"bun scripts/cli.ts codex trace collect [--root ~/.codex] [--output .state/codex-trace/<timestamp>] [--limit 30]",
|
||||
"bun scripts/cli.ts codex trace collect [--root ~/.codex] [--output /root/.unidesk/.state/codex-trace/<timestamp>] [--limit 30]",
|
||||
"bun scripts/cli.ts codex trace show --session <id> [--root ~/.codex] [--tail-bytes 12000]",
|
||||
"bun scripts/cli.ts codex deploy <commitId> # disabled legacy deployment entry",
|
||||
"bun scripts/cli.ts agentrun get tasks --queue commander --limit 20",
|
||||
@@ -484,7 +484,7 @@ function codexHelp(): unknown {
|
||||
active: "codex trace active finds open Codex session JSONL files from /proc without requiring lsof and prints copyable session ids.",
|
||||
grep: "codex trace grep supports --session <id>, --messages, --tools, --tool <name>, and --failed-only. It defaults to active/recent sessions, uses rg/raw prefiltering for speed, prioritizes messages/tool inputs, and folds tool outputs unless --include-output or -o wide is explicit.",
|
||||
output: "Default output is concise text/table; use -o json|yaml|name|wide for machine or wider output.",
|
||||
collectOutput: ".state/codex-trace/<timestamp>/manifest.json",
|
||||
collectOutput: "/root/.unidesk/.state/codex-trace/<timestamp>/manifest.json",
|
||||
note: "Use --root <dir> to scan another local Codex trace directory; collect copies bounded files locally and never uploads or deletes source files.",
|
||||
},
|
||||
unreadTriage: {
|
||||
@@ -544,7 +544,7 @@ function jobHelp(): unknown {
|
||||
"bun scripts/cli.ts jobs get <jobId|latest> [--tail-bytes N]",
|
||||
"bun scripts/cli.ts job cancel <jobId>",
|
||||
],
|
||||
description: "Inspect or cancel fire-and-forget job state from .state/jobs with structured progress summaries and bounded log tails. `jobs get/read` are compatibility aliases for `job status`; server lifecycle commands return this drill-down by default and reserve full JSON for --full/--raw.",
|
||||
description: "Inspect or cancel fire-and-forget job state from /root/.unidesk/.state/jobs with structured progress summaries and bounded log tails. `jobs get/read` are compatibility aliases for `job status`; server lifecycle commands return this drill-down by default and reserve full JSON for --full/--raw.",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ export function fakeModelProviderHelp(): Record<string, unknown> {
|
||||
],
|
||||
actions: {
|
||||
plan: "Read YAML configRefs and show local source/materialization and target k3s objects without mutation.",
|
||||
materialize: "Create/update local .state/secrets source files for provider auth, Codex config, and sentinel prompt set.",
|
||||
materialize: "Create/update local /root/.unidesk/.state/secrets source files for provider auth, Codex config, and sentinel prompt set.",
|
||||
apply: "Materialize local sources, then apply ConfigMap/Secret/Deployment/Service to the selected node k3s namespace.",
|
||||
status: "Inspect remote Deployment/Service/Secret/ConfigMap/pod readiness and /healthz without printing values.",
|
||||
smoke: "Exec a deterministic ECHO streaming and non-ECHO error check inside the fake provider pod.",
|
||||
@@ -941,9 +941,5 @@ function repoRelative(path: string): string {
|
||||
|
||||
function displayPath(path: string): string {
|
||||
if (path.startsWith(`${repoRoot}/`)) return path.slice(repoRoot.length + 1);
|
||||
const marker = "/.state/secrets/";
|
||||
const index = path.indexOf(marker);
|
||||
if (index >= 0) return `.state/secrets/${path.slice(index + marker.length)}`;
|
||||
if (path.endsWith("/.state/secrets")) return ".state/secrets";
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -321,11 +321,7 @@ export function monitorWebBuildkitStatePlan(cicd: Record<string, unknown>): Reco
|
||||
|
||||
export function secretSourcePaths(sourceRef: string): string[] {
|
||||
if (isAbsolute(sourceRef)) return [sourceRef];
|
||||
const paths = [join(repoRoot, ".state", "secrets", sourceRef)];
|
||||
const marker = "/.worktree/";
|
||||
const index = repoRoot.indexOf(marker);
|
||||
if (index >= 0) paths.push(join(repoRoot.slice(0, index), ".state", "secrets", sourceRef));
|
||||
return [...new Set(paths)];
|
||||
return [rootPath(".state", "secrets", sourceRef)];
|
||||
}
|
||||
|
||||
export function parseEnvFile(textValue: string): Record<string, string> {
|
||||
|
||||
@@ -2666,7 +2666,7 @@ function readSentinelSecretSourceValue(source: Record<string, unknown>): Record<
|
||||
const sourceKey = stringAt(source, "sourceKey");
|
||||
const sourceLine = numberAtNullable(source, "sourceLine");
|
||||
const paths = secretSourcePaths(sourceRef);
|
||||
const sourcePath = paths.find((item) => existsSync(item)) ?? paths[0] ?? join(repoRoot, ".state", "secrets", sourceRef);
|
||||
const sourcePath = paths.find((item) => existsSync(item)) ?? paths[0] ?? rootPath(".state", "secrets", sourceRef);
|
||||
if (!existsSync(sourcePath)) return { ok: false, error: "secret-source-missing", sourceRef, sourceKey, sourcePath: displayPath(sourcePath), valuesRedacted: true };
|
||||
const textValue = readFileSync(sourcePath, "utf8");
|
||||
const value = sourceLine === null ? parseEnvFile(textValue)[sourceKey] : textValue.split(/\r?\n/u)[sourceLine - 1]?.replace(/\r$/u, "");
|
||||
@@ -2688,7 +2688,7 @@ function readSentinelWebAccountUsername(source: Record<string, unknown>): { ok:
|
||||
const sourceLine = numberAtNullable(source, "usernameSourceLine");
|
||||
if (sourceRef === null || sourceLine === null) return { ok: false, error: "web-account-json-username-missing" };
|
||||
const paths = secretSourcePaths(sourceRef);
|
||||
const sourcePath = paths.find((item) => existsSync(item)) ?? paths[0] ?? join(repoRoot, ".state", "secrets", sourceRef);
|
||||
const sourcePath = paths.find((item) => existsSync(item)) ?? paths[0] ?? rootPath(".state", "secrets", sourceRef);
|
||||
if (!existsSync(sourcePath)) return { ok: false, error: "web-account-json-username-source-missing" };
|
||||
const value = readFileSync(sourcePath, "utf8").split(/\r?\n/u)[sourceLine - 1]?.replace(/\r$/u, "") ?? "";
|
||||
return value.length === 0 ? { ok: false, error: "web-account-json-username-line-missing" } : { ok: true, value };
|
||||
@@ -2720,7 +2720,7 @@ function readSentinelFrpcMaterial(state: SentinelCicdState): Record<string, unkn
|
||||
const sourceRef = stringAt(state.publicExposure, "frpc.tokenSourceRef");
|
||||
const sourceKey = stringAt(state.publicExposure, "frpc.tokenSourceKey");
|
||||
const paths = secretSourcePaths(sourceRef);
|
||||
const sourcePath = paths.find((item) => existsSync(item)) ?? paths[0] ?? join(repoRoot, ".state", "secrets", sourceRef);
|
||||
const sourcePath = paths.find((item) => existsSync(item)) ?? paths[0] ?? rootPath(".state", "secrets", sourceRef);
|
||||
if (!existsSync(sourcePath)) return { ok: false, error: "frp-token-source-missing", sourceRef, sourceKey, sourcePath: displayPath(sourcePath), valuesRedacted: true };
|
||||
const values = parseEnvFile(readFileSync(sourcePath, "utf8"));
|
||||
const token = values[sourceKey];
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// Responsibility: Quick-verify observe orchestration and artifact interpretation for web-probe sentinel P5 validation.
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import type { CommandResult } from "./command";
|
||||
import { runCommand } from "./command";
|
||||
import { resolveCliChildJsonCommandResult } from "./cli-child-json-recovery";
|
||||
@@ -1959,7 +1958,7 @@ function readPromptSetForScenario(state: SentinelCicdState, scenario: Record<str
|
||||
const sourceRef = stringAt(promptSet, "promptSourceRef");
|
||||
const key = stringAt(promptSet, "promptSourceKey");
|
||||
const paths = secretSourcePaths(sourceRef);
|
||||
const sourcePath = paths.find((item) => existsSync(item)) ?? paths[0] ?? join(repoRoot, ".state", "secrets", sourceRef);
|
||||
const sourcePath = paths.find((item) => existsSync(item)) ?? paths[0] ?? rootPath(".state", "secrets", sourceRef);
|
||||
const summary = { sourceRef, sourceKey: key, sourcePath: displayPath(sourcePath), valuesRedacted: true };
|
||||
const runtimeRaw = process.env[key];
|
||||
const values = existsSync(sourcePath) ? parseEnvFile(readFileSync(sourcePath, "utf8")) : {};
|
||||
|
||||
@@ -493,7 +493,7 @@ export function runNodePublicExposure(options: NodePublicExposureOptions): Recor
|
||||
source,
|
||||
mutation: false,
|
||||
valuesRedacted: true,
|
||||
next: { fixSecretSource: `create .state/secrets/${exposure.tokenSourceRef} with ${exposure.tokenSourceKey}=<redacted>` },
|
||||
next: { fixSecretSource: `create ${rootPath(".state", "secrets", exposure.tokenSourceRef)} with ${exposure.tokenSourceKey}=<redacted>` },
|
||||
};
|
||||
}
|
||||
const caddyResult = runTransHostScript(exposure.caddyRoute, publicExposureCaddyScript(options, exposure), "", options.timeoutSeconds);
|
||||
@@ -581,7 +581,7 @@ function publicExposureSecretApplyStatus(options: NodePublicExposureOptions, exp
|
||||
|
||||
export function readPublicExposureTokenSource(exposure: HwlabRuntimePublicExposureSpec): { ok: boolean; path: string; checkedPaths: string[]; key: string; value: string | null; fingerprint: string | null; error?: string } {
|
||||
const checkedPaths = publicExposureTokenSourcePaths(exposure);
|
||||
const path = checkedPaths.find((candidate) => existsSync(candidate)) ?? checkedPaths[0] ?? join(repoRoot, ".state", "secrets", exposure.tokenSourceRef);
|
||||
const path = checkedPaths.find((candidate) => existsSync(candidate)) ?? checkedPaths[0] ?? rootPath(".state", "secrets", exposure.tokenSourceRef);
|
||||
if (!existsSync(path)) return { ok: false, path, checkedPaths, key: exposure.tokenSourceKey, value: null, fingerprint: null, error: "secret-source-missing" };
|
||||
const values = parseEnvFile(readFileSync(path, "utf8"));
|
||||
const value = values[exposure.tokenSourceKey];
|
||||
@@ -597,11 +597,7 @@ export function readPublicExposureTokenSource(exposure: HwlabRuntimePublicExposu
|
||||
}
|
||||
|
||||
export function publicExposureTokenSourcePaths(exposure: HwlabRuntimePublicExposureSpec): string[] {
|
||||
const paths = [join(repoRoot, ".state", "secrets", exposure.tokenSourceRef)];
|
||||
const marker = "/.worktree/";
|
||||
const index = repoRoot.indexOf(marker);
|
||||
if (index >= 0) paths.push(join(repoRoot.slice(0, index), ".state", "secrets", exposure.tokenSourceRef));
|
||||
return [...new Set(paths)];
|
||||
return [rootPath(".state", "secrets", exposure.tokenSourceRef)];
|
||||
}
|
||||
|
||||
export function publicExposureSecretStatus(fields: Record<string, string>, result: CommandResult): Record<string, unknown> {
|
||||
|
||||
@@ -740,15 +740,7 @@ export function readLocalPostgresPasswordMaterial(input: { sourceRef: string; so
|
||||
|
||||
export function localSecretSourcePaths(sourceRef: string): string[] {
|
||||
if (isAbsolute(sourceRef)) return [sourceRef];
|
||||
const marker = "/.worktree/";
|
||||
const index = repoRoot.indexOf(marker);
|
||||
const paths = index >= 0
|
||||
? [
|
||||
join(repoRoot.slice(0, index), ".state", "secrets", sourceRef),
|
||||
join(repoRoot, ".state", "secrets", sourceRef),
|
||||
]
|
||||
: [join(repoRoot, ".state", "secrets", sourceRef)];
|
||||
return [...new Set(paths)];
|
||||
return [rootPath(".state", "secrets", sourceRef)];
|
||||
}
|
||||
|
||||
export function shortSecretFingerprint(value: string): string {
|
||||
@@ -1274,7 +1266,7 @@ export function externalPostgresSecretSetupScript(spec: HwlabRuntimeLaneSpec, dr
|
||||
|
||||
export function externalPostgresSecretSourceRoot(spec: HwlabRuntimeLaneSpec): string {
|
||||
const configRef = spec.externalPostgres?.configRef;
|
||||
if (configRef === undefined) return join(repoRoot, ".state", "secrets");
|
||||
if (configRef === undefined) return rootPath(".state", "secrets");
|
||||
const configPath = rootPath(configRef);
|
||||
const parsed = Bun.YAML.parse(readFileSync(configPath, "utf8")) as unknown;
|
||||
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`${configRef} must be a YAML object`);
|
||||
@@ -1296,11 +1288,7 @@ export function readSecretSourceValue(secretRoot: string, sourceRef: string, key
|
||||
|
||||
export function secretSourcePaths(sourceRef: string): string[] {
|
||||
if (isAbsolute(sourceRef)) return [sourceRef];
|
||||
const paths = [join(repoRoot, ".state", "secrets", sourceRef)];
|
||||
const marker = "/.worktree/";
|
||||
const index = repoRoot.indexOf(marker);
|
||||
if (index >= 0) paths.push(join(repoRoot.slice(0, index), ".state", "secrets", sourceRef));
|
||||
return [...new Set(paths)];
|
||||
return [rootPath(".state", "secrets", sourceRef)];
|
||||
}
|
||||
|
||||
export function displayRepoPath(path: string): string {
|
||||
@@ -1392,7 +1380,7 @@ function readBootstrapAdminUsername(spec: RuntimeSecretSpec): { ok: true; value:
|
||||
|
||||
function readSecretSourceScalar(sourceRef: string, sourceKey: string, sourceLine: number | null): { ok: true; value: string; sourcePath: string; sourcePresent: boolean } | { ok: false; sourcePath: string; sourcePresent: boolean; error: string } {
|
||||
const paths = secretSourcePaths(sourceRef);
|
||||
const sourcePath = paths.find((candidate) => existsSync(candidate)) ?? paths[0] ?? join(repoRoot, ".state", "secrets", sourceRef);
|
||||
const sourcePath = paths.find((candidate) => existsSync(candidate)) ?? paths[0] ?? rootPath(".state", "secrets", sourceRef);
|
||||
const runtimeValue = process.env[sourceKey];
|
||||
if (!existsSync(sourcePath)) {
|
||||
if (runtimeValue !== undefined && runtimeValue.length > 0) return { ok: true, value: runtimeValue, sourcePath, sourcePresent: false };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { runCommand } from "./command";
|
||||
import { type UniDeskConfig, repoRoot } from "./config";
|
||||
import { type UniDeskConfig, repoRoot, rootPath } from "./config";
|
||||
import { jsonByteLength, previewJson } from "./preview";
|
||||
|
||||
// Todo Note misleading-404 rewrite (issue #198) for legacy deployments.
|
||||
@@ -279,7 +279,7 @@ export function coreInternalFetch(path: string, init?: { method?: string; body?:
|
||||
const result = runCommand(command, repoRoot, { timeoutMs: init?.timeoutMs });
|
||||
if (result.exitCode !== 0) {
|
||||
if (backendCoreContainerMissing(result.stderr)) {
|
||||
const envPath = join(repoRoot, ".state", "docker-compose.env");
|
||||
const envPath = rootPath(".state", "docker-compose.env");
|
||||
return backendCoreUnavailableDiagnostic({
|
||||
exitCode: result.exitCode,
|
||||
stdoutTail: result.stdout.slice(-1200),
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
||||
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname } from "node:path";
|
||||
import type { UniDeskConfig } from "./config";
|
||||
import { rootPath } from "./config";
|
||||
import { rootPath, stateRoot } from "./config";
|
||||
import { runCommand, type CommandResult } from "./command";
|
||||
import { resolveEgressProxySourceRef, type MasterShadowsocksSourceSpec } from "./egress-proxy-sources";
|
||||
import type { RenderedCliResult } from "./output";
|
||||
@@ -332,11 +332,11 @@ function managedClientSpec(raw: Record<string, unknown>, label: string): HostPro
|
||||
mode,
|
||||
upstreamUrl: httpsUrlField(raw, "upstreamUrl", label),
|
||||
version: stringField(raw, "version", label),
|
||||
archiveCachePath: repoStatePathField(raw, "archiveCachePath", label),
|
||||
archiveCachePath: canonicalStatePathField(raw, "archiveCachePath", label),
|
||||
archiveSha256: sha256Field(raw, "archiveSha256", label),
|
||||
archiveInstallPath: absolutePathField(raw, "archiveInstallPath", label),
|
||||
binaryMember: relativePathField(raw, "binaryMember", label),
|
||||
binaryCachePath: repoStatePathField(raw, "binaryCachePath", label),
|
||||
binaryCachePath: canonicalStatePathField(raw, "binaryCachePath", label),
|
||||
binarySha256: sha256Field(raw, "binarySha256", label),
|
||||
installPath: absolutePathField(raw, "installPath", label),
|
||||
configPath: absolutePathField(raw, "configPath", label),
|
||||
@@ -1314,10 +1314,10 @@ function repoYamlPathField(obj: Record<string, unknown>, key: string, label: str
|
||||
return value;
|
||||
}
|
||||
|
||||
function repoStatePathField(obj: Record<string, unknown>, key: string, label: string): string {
|
||||
function canonicalStatePathField(obj: Record<string, unknown>, key: string, label: string): string {
|
||||
const value = stringField(obj, key, label);
|
||||
if (value.startsWith("/") || value.includes("..") || !value.startsWith(".state/")) {
|
||||
throw new Error(`${label}.${key} must be a repo-relative .state/ path without ..`);
|
||||
if (value.includes("..") || !value.startsWith(`${stateRoot}/`)) {
|
||||
throw new Error(`${label}.${key} must be under ${stateRoot}/ without ..`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { chromium, firefox, webkit, type Browser, type BrowserContext, type BrowserType, type Page } from "playwright";
|
||||
import { repoRoot } from "./config";
|
||||
import { repoRoot, rootPath } from "./config";
|
||||
import { emitError, emitJson } from "./output";
|
||||
|
||||
type BrowserName = "chromium" | "firefox" | "webkit";
|
||||
@@ -22,7 +22,7 @@ interface ParsedOptions {
|
||||
fullPage: boolean;
|
||||
}
|
||||
|
||||
const stateRoot = join(repoRoot, ".state", "playwright-cli");
|
||||
const stateRoot = rootPath(".state", "playwright-cli");
|
||||
const defaultScreenshotPath = join(stateRoot, "screenshots", "latest.png");
|
||||
const supportedCommands = ["open", "screenshot", "eval", "session-list", "session-delete"];
|
||||
const unsupportedInteractiveCommands = new Set([
|
||||
|
||||
@@ -83,7 +83,7 @@ function parseAttachOptions(config: UniDeskConfig, args: string[]): ProviderAtta
|
||||
}
|
||||
const envFile = optionValue(args, "--env-file") ?? rootPath(".state", `provider-${providerId}.env`);
|
||||
const composeFile = optionValue(args, "--compose-file") ?? rootPath(`provider-${providerId}.yml`);
|
||||
const logDir = optionValue(args, "--log-dir") ?? rootPath("logs", `provider-${providerId}`);
|
||||
const logDir = optionValue(args, "--log-dir") ?? rootPath(".state", "logs", `provider-${providerId}`);
|
||||
return {
|
||||
providerId,
|
||||
masterServer: optionValue(args, "--master-server") ?? optionValue(args, "--master") ?? defaultMasterServer(config),
|
||||
@@ -109,7 +109,7 @@ function envContent(options: ProviderAttachOptions): string {
|
||||
`PROVIDER_UPGRADE_HOST_PROJECT_ROOT=${repoRoot}`,
|
||||
"PROVIDER_UPGRADE_WORKSPACE_PATH=/workspace",
|
||||
`PROVIDER_UPGRADE_COMPOSE_FILE=provider-${options.providerId}.yml`,
|
||||
`PROVIDER_UPGRADE_ENV_FILE=.state/provider-${options.providerId}.env`,
|
||||
`PROVIDER_UPGRADE_ENV_FILE=${options.envFile}`,
|
||||
`PROVIDER_UPGRADE_COMPOSE_PROJECT=unidesk-${slug}`,
|
||||
"PROVIDER_UPGRADE_SERVICE=provider-gateway",
|
||||
`PROVIDER_UPGRADE_RUNNER_IMAGE=unidesk_provider-gateway:${slug}`,
|
||||
|
||||
@@ -228,7 +228,7 @@ pub async fn codex_queue_load_test(
|
||||
chrono::Utc::now().timestamp_millis(),
|
||||
rand::random::<u32>()
|
||||
);
|
||||
let dir = ".state/code-queue-perf";
|
||||
let dir = "/root/.unidesk/.state/code-queue-perf";
|
||||
let output_path = format!("{dir}/{run_id}.json");
|
||||
let stderr_path = format!("{dir}/{run_id}.stderr");
|
||||
let exit_path = format!("{dir}/{run_id}.exit");
|
||||
@@ -239,7 +239,7 @@ pub async fn codex_queue_load_test(
|
||||
.map(|value| format!(" --url {}", shell_quote(value)))
|
||||
.unwrap_or_default();
|
||||
let start_command = format!(
|
||||
"mkdir -p {}; rm -f {} {} {}; (PLAYWRIGHT_BROWSERS_PATH=.state/playwright-browsers bun scripts/src/code-queue-perf.ts --json --timeout-ms {} --target-ms {}{} > {}; printf '%s' \"$?\" > {}) > {} 2>&1 & printf '%s\\n' {}",
|
||||
"mkdir -p {}; rm -f {} {} {}; (PLAYWRIGHT_BROWSERS_PATH=/root/.unidesk/.state/playwright-browsers bun scripts/src/code-queue-perf.ts --json --timeout-ms {} --target-ms {}{} > {}; printf '%s' \"$?\" > {}) > {} 2>&1 & printf '%s\\n' {}",
|
||||
shell_quote(dir),
|
||||
shell_quote(&output_path),
|
||||
shell_quote(&stderr_path),
|
||||
|
||||
@@ -237,8 +237,8 @@ export async function codexQueueLoadTest(req: Request): Promise<Response> {
|
||||
const timeoutMs = numberFromUnknown(body.timeoutMs, 90_000, 5_000, 180_000);
|
||||
const targetMs = numberFromUnknown(body.targetMs, 1_000, 100, 60_000);
|
||||
const runId = safePerfRunId();
|
||||
const dir = ".state/code-queue-perf";
|
||||
const browsersPath = ".state/playwright-browsers";
|
||||
const dir = "/root/.unidesk/.state/code-queue-perf";
|
||||
const browsersPath = "/root/.unidesk/.state/playwright-browsers";
|
||||
const outputPath = `${dir}/${runId}.json`;
|
||||
const stderrPath = `${dir}/${runId}.stderr`;
|
||||
const exitPath = `${dir}/${runId}.exit`;
|
||||
|
||||
@@ -253,11 +253,11 @@ spec:
|
||||
type: Directory
|
||||
- name: logs
|
||||
hostPath:
|
||||
path: /root/unidesk/.state/code-queue/logs
|
||||
path: /root/.unidesk/.state/code-queue/logs
|
||||
type: DirectoryOrCreate
|
||||
- name: state
|
||||
hostPath:
|
||||
path: /root/unidesk/.state/code-queue
|
||||
path: /root/.unidesk/.state/code-queue
|
||||
type: DirectoryOrCreate
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
@@ -505,11 +505,11 @@ spec:
|
||||
type: Directory
|
||||
- name: logs
|
||||
hostPath:
|
||||
path: /root/unidesk/.state/code-queue/logs
|
||||
path: /root/.unidesk/.state/code-queue/logs
|
||||
type: DirectoryOrCreate
|
||||
- name: state
|
||||
hostPath:
|
||||
path: /root/unidesk/.state/code-queue
|
||||
path: /root/.unidesk/.state/code-queue
|
||||
type: DirectoryOrCreate
|
||||
---
|
||||
apiVersion: v1
|
||||
@@ -1219,11 +1219,11 @@ spec:
|
||||
type: Directory
|
||||
- name: logs
|
||||
hostPath:
|
||||
path: /root/unidesk/.state/code-queue/logs
|
||||
path: /root/.unidesk/.state/code-queue/logs
|
||||
type: DirectoryOrCreate
|
||||
- name: state
|
||||
hostPath:
|
||||
path: /root/unidesk/.state/code-queue
|
||||
path: /root/.unidesk/.state/code-queue
|
||||
type: DirectoryOrCreate
|
||||
---
|
||||
apiVersion: v1
|
||||
|
||||
Reference in New Issue
Block a user