fix: use owner-level unidesk state
This commit is contained in:
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
.state/
|
.state
|
||||||
logs/
|
logs
|
||||||
node_modules/
|
node_modules/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
# Non-secret local runtime hints copied by `bun scripts/cli.ts dev-env worktree add`.
|
# 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.yaml
|
||||||
config/*.local.yml
|
config/*.local.yml
|
||||||
config/*.local.json
|
config/*.local.json
|
||||||
|
|||||||
+3
-3
@@ -78,7 +78,7 @@
|
|||||||
"hostProjectRoot": "/root/unidesk",
|
"hostProjectRoot": "/root/unidesk",
|
||||||
"workspacePath": "/workspace",
|
"workspacePath": "/workspace",
|
||||||
"composeFile": "docker-compose.yml",
|
"composeFile": "docker-compose.yml",
|
||||||
"composeEnvFile": ".state/docker-compose.env",
|
"composeEnvFile": "/root/.unidesk/.state/docker-compose.env",
|
||||||
"composeProject": "unidesk",
|
"composeProject": "unidesk",
|
||||||
"service": "provider-gateway",
|
"service": "provider-gateway",
|
||||||
"runnerImage": "unidesk_provider-gateway"
|
"runnerImage": "unidesk_provider-gateway"
|
||||||
@@ -987,8 +987,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"stateDir": ".state",
|
"stateDir": "/root/.unidesk/.state",
|
||||||
"logsDir": "logs",
|
"logsDir": "/root/.unidesk/.state/logs",
|
||||||
"docsReferenceDir": "docs/reference"
|
"docsReferenceDir": "docs/reference"
|
||||||
},
|
},
|
||||||
"sshForwarding": {
|
"sshForwarding": {
|
||||||
|
|||||||
@@ -398,7 +398,7 @@ controlPlane:
|
|||||||
key: GH_TOKEN
|
key: GH_TOKEN
|
||||||
unideskSshToken:
|
unideskSshToken:
|
||||||
id: tool-unidesk-ssh-token
|
id: tool-unidesk-ssh-token
|
||||||
sourceRef: /root/unidesk/.state/docker-compose.env
|
sourceRef: /root/.unidesk/.state/docker-compose.env
|
||||||
sourceKey: UNIDESK_SSH_CLIENT_TOKEN
|
sourceKey: UNIDESK_SSH_CLIENT_TOKEN
|
||||||
targetRef:
|
targetRef:
|
||||||
namespace: agentrun-v02
|
namespace: agentrun-v02
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ defaults:
|
|||||||
identityId: github.com
|
identityId: github.com
|
||||||
|
|
||||||
sources:
|
sources:
|
||||||
root: /root/unidesk/.state/secrets
|
root: /root/.unidesk/.state/secrets
|
||||||
|
|
||||||
identities:
|
identities:
|
||||||
github.com:
|
github.com:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ metadata:
|
|||||||
- 2187
|
- 2187
|
||||||
|
|
||||||
sources:
|
sources:
|
||||||
root: /root/unidesk/.state/secrets
|
root: /root/.unidesk/.state/secrets
|
||||||
files:
|
files:
|
||||||
- sourceRef: hwlab/d601-v03-preset-users.env
|
- sourceRef: hwlab/d601-v03-preset-users.env
|
||||||
type: env
|
type: env
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ metadata:
|
|||||||
- 1237
|
- 1237
|
||||||
- 1236
|
- 1236
|
||||||
|
|
||||||
sourceRoot: /root/unidesk/.state/secrets
|
sourceRoot: /root/.unidesk/.state/secrets
|
||||||
|
|
||||||
targets:
|
targets:
|
||||||
- id: d601-v03
|
- id: d601-v03
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ postgres:
|
|||||||
|
|
||||||
secrets:
|
secrets:
|
||||||
source: master-local
|
source: master-local
|
||||||
root: /root/unidesk/.state/secrets
|
root: /root/.unidesk/.state/secrets
|
||||||
entries:
|
entries:
|
||||||
- name: agentrun-nc01-v02-db-credentials
|
- name: agentrun-nc01-v02-db-credentials
|
||||||
sourceRef: platform-db/agentrun-nc01-v02-db.env
|
sourceRef: platform-db/agentrun-nc01-v02-db.env
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ postgres:
|
|||||||
|
|
||||||
secrets:
|
secrets:
|
||||||
source: master-local
|
source: master-local
|
||||||
root: /root/unidesk/.state/secrets
|
root: /root/.unidesk/.state/secrets
|
||||||
entries:
|
entries:
|
||||||
- name: sub2api-db-credentials
|
- name: sub2api-db-credentials
|
||||||
sourceRef: platform-db/sub2api-db.env
|
sourceRef: platform-db/sub2api-db.env
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ app:
|
|||||||
publicExposure:
|
publicExposure:
|
||||||
enabled: true
|
enabled: true
|
||||||
publicBaseUrl: https://gitea.pikapython.com
|
publicBaseUrl: https://gitea.pikapython.com
|
||||||
secretRoot: /root/unidesk/.state/secrets
|
secretRoot: /root/.unidesk/.state/secrets
|
||||||
dns:
|
dns:
|
||||||
hostname: gitea.pikapython.com
|
hostname: gitea.pikapython.com
|
||||||
expectedA: 82.156.23.220
|
expectedA: 82.156.23.220
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ server:
|
|||||||
serviceName: sub2api-master-egress-proxy
|
serviceName: sub2api-master-egress-proxy
|
||||||
containerName: unidesk-sub2api-master-egress-proxy
|
containerName: unidesk-sub2api-master-egress-proxy
|
||||||
image: ghcr.io/shadowsocks/ssserver-rust:latest
|
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
|
listenHost: 0.0.0.0
|
||||||
listenPort: 18792
|
listenPort: 18792
|
||||||
health:
|
health:
|
||||||
@@ -95,11 +95,11 @@ templates:
|
|||||||
mode: trans-static-binary
|
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
|
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
|
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
|
archiveSha256: f48703461a15476951ac4967cdad339d986f4b8096b4eb3ff0829a500502d697
|
||||||
archiveInstallPath: /var/cache/unidesk/host-egress-proxy/sing-box-1.13.14-linux-amd64.tar.gz
|
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
|
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
|
binarySha256: 68aeab83cc4ab2659a5b92232261a20746ccdafc3b3d1e19b2d63247eec3bbf7
|
||||||
installPath: /usr/local/bin/sing-box
|
installPath: /usr/local/bin/sing-box
|
||||||
configPath: /etc/unidesk/host-egress-proxy/sing-box.json
|
configPath: /etc/unidesk/host-egress-proxy/sing-box.json
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ runtime:
|
|||||||
sslMode: require
|
sslMode: require
|
||||||
appDatabaseValue: langbot?ssl=require
|
appDatabaseValue: langbot?ssl=require
|
||||||
secrets:
|
secrets:
|
||||||
root: /root/unidesk/.state/secrets
|
root: /root/.unidesk/.state/secrets
|
||||||
appSourceRef: platform-infra/langbot.env
|
appSourceRef: platform-infra/langbot.env
|
||||||
storage:
|
storage:
|
||||||
data:
|
data:
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ runtime:
|
|||||||
sslMode: require
|
sslMode: require
|
||||||
sslRejectUnauthorized: false
|
sslRejectUnauthorized: false
|
||||||
secrets:
|
secrets:
|
||||||
root: /root/unidesk/.state/secrets
|
root: /root/.unidesk/.state/secrets
|
||||||
appSourceRef: platform-infra/n8n.env
|
appSourceRef: platform-infra/n8n.env
|
||||||
encryptionKey: N8N_ENCRYPTION_KEY
|
encryptionKey: N8N_ENCRYPTION_KEY
|
||||||
storage:
|
storage:
|
||||||
|
|||||||
@@ -12,5 +12,5 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "0.0.0.0:18792:18792/tcp"
|
- "0.0.0.0:18792:18792/tcp"
|
||||||
volumes:
|
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
|
logging: *unidesk-log-rotation
|
||||||
|
|||||||
@@ -441,7 +441,7 @@ runtime:
|
|||||||
sslMode: require
|
sslMode: require
|
||||||
pendingAllowed: true
|
pendingAllowed: true
|
||||||
secrets:
|
secrets:
|
||||||
root: /root/unidesk/.state/secrets
|
root: /root/.unidesk/.state/secrets
|
||||||
appSourceRef: platform-infra/sub2api.env
|
appSourceRef: platform-infra/sub2api.env
|
||||||
redis:
|
redis:
|
||||||
serviceName: sub2api-redis
|
serviceName: sub2api-redis
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ n8n:
|
|||||||
|
|
||||||
archiveCallback:
|
archiveCallback:
|
||||||
publicUrl: http://74.48.78.17:18081/webhooks/wechat-archive
|
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
|
tokenSourceRef: platform-infra/wechat-archive.env
|
||||||
tokenKey: UNIDESK_WECHAT_ARCHIVE_TOKEN
|
tokenKey: UNIDESK_WECHAT_ARCHIVE_TOKEN
|
||||||
timeoutMs: 90000
|
timeoutMs: 90000
|
||||||
@@ -160,7 +160,7 @@ baiduNetdisk:
|
|||||||
proxyMode: backend-core-microservice-proxy
|
proxyMode: backend-core-microservice-proxy
|
||||||
configRef: config.json#microservices.baidu-netdisk
|
configRef: config.json#microservices.baidu-netdisk
|
||||||
staging:
|
staging:
|
||||||
hostRoot: /root/unidesk/.state/baidu-netdisk/staging
|
hostRoot: /root/.unidesk/.state/baidu-netdisk/staging
|
||||||
containerRoot: /data/staging
|
containerRoot: /data/staging
|
||||||
outboxDir: wechat-archive/outbox
|
outboxDir: wechat-archive/outbox
|
||||||
pullDir: wechat-archive/pulls
|
pullDir: wechat-archive/pulls
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ metadata:
|
|||||||
- 2256
|
- 2256
|
||||||
|
|
||||||
sources:
|
sources:
|
||||||
root: /root/unidesk/.state/secrets
|
root: /root/.unidesk/.state/secrets
|
||||||
files:
|
files:
|
||||||
- sourceRef: platform-db/langbot-db.env
|
- sourceRef: platform-db/langbot-db.env
|
||||||
type: env
|
type: env
|
||||||
|
|||||||
+15
-15
@@ -62,27 +62,27 @@ gc:
|
|||||||
enabled: false
|
enabled: false
|
||||||
keepDays: 14
|
keepDays: 14
|
||||||
fileRoots:
|
fileRoots:
|
||||||
e2e: .state/e2e
|
e2e: /root/.unidesk/.state/e2e
|
||||||
validation: .state/validation
|
validation: /root/.unidesk/.state/validation
|
||||||
jobs: .state/jobs
|
jobs: /root/.unidesk/.state/jobs
|
||||||
codex-queue-output-archive: .state/codex-queue/output-archive
|
codex-queue-output-archive: /root/.unidesk/.state/codex-queue/output-archive
|
||||||
dirRoots:
|
dirRoots:
|
||||||
deploy-exports: .state/deploy/exports
|
deploy-exports: /root/.unidesk/.state/deploy/exports
|
||||||
deploy-resolve: .state/deploy/resolve
|
deploy-resolve: /root/.unidesk/.state/deploy/resolve
|
||||||
stateStaleScratch:
|
stateStaleScratch:
|
||||||
enabled: false
|
enabled: false
|
||||||
keepHours: 24
|
keepHours: 24
|
||||||
fileRoots:
|
fileRoots:
|
||||||
playwright-cli-screenshots: .state/playwright-cli/screenshots
|
playwright-cli-screenshots: /root/.unidesk/.state/playwright-cli/screenshots
|
||||||
playwright-cli-sessions: .state/playwright-cli/sessions
|
playwright-cli-sessions: /root/.unidesk/.state/playwright-cli/sessions
|
||||||
perf: .state/perf
|
perf: /root/.unidesk/.state/perf
|
||||||
tmp: .state/tmp
|
tmp: /root/.unidesk/.state/tmp
|
||||||
web-observe: .state/web-observe
|
web-observe: /root/.unidesk/.state/web-observe
|
||||||
dirRoots:
|
dirRoots:
|
||||||
hwlab-cd: .state/hwlab-cd
|
hwlab-cd: /root/.unidesk/.state/hwlab-cd
|
||||||
codex-queue-stats-verify: .state/codex-queue-stats-verify
|
codex-queue-stats-verify: /root/.unidesk/.state/codex-queue-stats-verify
|
||||||
codex-queue-perf: .state/codex-queue-perf
|
codex-queue-perf: /root/.unidesk/.state/codex-queue-perf
|
||||||
tmp: .state/tmp
|
tmp: /root/.unidesk/.state/tmp
|
||||||
legacyDockerImages:
|
legacyDockerImages:
|
||||||
enabled: true
|
enabled: true
|
||||||
minAgeHours: 12
|
minAgeHours: 12
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ services:
|
|||||||
logFile: /var/log/unidesk/decision-center.jsonl
|
logFile: /var/log/unidesk/decision-center.jsonl
|
||||||
database:
|
database:
|
||||||
sourceRef:
|
sourceRef:
|
||||||
path: .state/secrets/decision-center/nc01-db.env
|
path: /root/.unidesk/.state/secrets/decision-center/nc01-db.env
|
||||||
key: DATABASE_URL
|
key: DATABASE_URL
|
||||||
secretName: decision-center-runtime-secrets
|
secretName: decision-center-runtime-secrets
|
||||||
secretKey: DATABASE_URL
|
secretKey: DATABASE_URL
|
||||||
@@ -162,7 +162,7 @@ services:
|
|||||||
logFile: /var/log/unidesk/todo-note.jsonl
|
logFile: /var/log/unidesk/todo-note.jsonl
|
||||||
database:
|
database:
|
||||||
sourceRef:
|
sourceRef:
|
||||||
path: .state/secrets/decision-center/nc01-db.env
|
path: /root/.unidesk/.state/secrets/decision-center/nc01-db.env
|
||||||
key: DATABASE_URL
|
key: DATABASE_URL
|
||||||
secretName: decision-center-runtime-secrets
|
secretName: decision-center-runtime-secrets
|
||||||
secretKey: DATABASE_URL
|
secretKey: DATABASE_URL
|
||||||
@@ -240,7 +240,7 @@ runtimeAuth:
|
|||||||
|
|
||||||
runtimeSecrets:
|
runtimeSecrets:
|
||||||
sourceRef:
|
sourceRef:
|
||||||
path: .state/secrets/unidesk-host-k8s.env
|
path: /root/.unidesk/.state/secrets/unidesk-host-k8s.env
|
||||||
target:
|
target:
|
||||||
name: unidesk-runtime-secrets
|
name: unidesk-runtime-secrets
|
||||||
keys:
|
keys:
|
||||||
|
|||||||
+4
-4
@@ -87,7 +87,7 @@ services:
|
|||||||
UNIDESK_LOG_RETENTION_BYTES: "${UNIDESK_LOG_RETENTION_BYTES:-512MiB}"
|
UNIDESK_LOG_RETENTION_BYTES: "${UNIDESK_LOG_RETENTION_BYTES:-512MiB}"
|
||||||
volumes:
|
volumes:
|
||||||
- ${UNIDESK_LOG_DIR}:/var/log/unidesk
|
- ${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:
|
healthcheck:
|
||||||
test:
|
test:
|
||||||
- "CMD-SHELL"
|
- "CMD-SHELL"
|
||||||
@@ -171,7 +171,7 @@ services:
|
|||||||
UNIDESK_LOG_RETENTION_BYTES: "${UNIDESK_LOG_RETENTION_BYTES:-512MiB}"
|
UNIDESK_LOG_RETENTION_BYTES: "${UNIDESK_LOG_RETENTION_BYTES:-512MiB}"
|
||||||
volumes:
|
volumes:
|
||||||
- ${UNIDESK_LOG_DIR}:/var/log/unidesk
|
- ${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:
|
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))"]
|
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
|
interval: 5s
|
||||||
@@ -362,7 +362,7 @@ services:
|
|||||||
UNIDESK_DEPLOY_REQUESTED_COMMIT: "${UNIDESK_BAIDU_NETDISK_DEPLOY_REQUESTED_COMMIT:-}"
|
UNIDESK_DEPLOY_REQUESTED_COMMIT: "${UNIDESK_BAIDU_NETDISK_DEPLOY_REQUESTED_COMMIT:-}"
|
||||||
volumes:
|
volumes:
|
||||||
- ${UNIDESK_LOG_DIR}:/var/log/unidesk
|
- ${UNIDESK_LOG_DIR}:/var/log/unidesk
|
||||||
- ./.state/baidu-netdisk/staging:/data/staging
|
- /root/.unidesk/.state/baidu-netdisk/staging:/data/staging
|
||||||
healthcheck:
|
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))"]
|
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
|
interval: 5s
|
||||||
@@ -493,7 +493,7 @@ services:
|
|||||||
command: ["bun", "/workspace/scripts/runtime/pk01-postgres-relay.js"]
|
command: ["bun", "/workspace/scripts/runtime/pk01-postgres-relay.js"]
|
||||||
volumes:
|
volumes:
|
||||||
- .:/workspace:ro
|
- .:/workspace:ro
|
||||||
- ${UNIDESK_LOG_DIR:-./.state/logs}:/var/log/unidesk
|
- ${UNIDESK_LOG_DIR:-/root/.unidesk/.state/logs}:/var/log/unidesk
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "bun", "/workspace/scripts/runtime/pk01-postgres-relay.js", "--healthcheck"]
|
test: ["CMD", "bun", "/workspace/scripts/runtime/pk01-postgres-relay.js", "--healthcheck"]
|
||||||
interval: 10s
|
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
|
## 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.
|
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.
|
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
|
## Decision Center
|
||||||
|
|
||||||
|
|||||||
@@ -435,7 +435,7 @@ export function agentRunLaneSummary(spec: AgentRunLaneSpec): Record<string, unkn
|
|||||||
},
|
},
|
||||||
secrets: spec.secrets.map((secret) => ({
|
secrets: spec.secrets.map((secret) => ({
|
||||||
id: secret.id,
|
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,
|
sourceMode: secret.sourceMode,
|
||||||
sourceKey: secret.sourceKey,
|
sourceKey: secret.sourceKey,
|
||||||
sourceFormat: secret.sourceFormat,
|
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`);
|
if (value.length === 0) throw new Error(`secret source ${sourceRef} is empty`);
|
||||||
value = transformSecretSourceValue(spec, source, value);
|
value = transformSecretSourceValue(spec, source, value);
|
||||||
return {
|
return {
|
||||||
redactedPath: source.sourceMode === "codex-config" ? sourceRef : sourceRef.startsWith("/") ? redactAbsoluteSecretPath(sourceRef) : `.state/secrets/${sourceRef}`,
|
redactedPath: source.sourceMode === "codex-config" ? sourceRef : redactAbsoluteSecretPath(sourcePath ?? sourceRef),
|
||||||
value,
|
value,
|
||||||
valueBytes: Buffer.byteLength(value, "utf8"),
|
valueBytes: Buffer.byteLength(value, "utf8"),
|
||||||
fingerprint: sha256Fingerprint(value),
|
fingerprint: sha256Fingerprint(value),
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export function verifyLocalArtifactLabels(
|
|||||||
|
|
||||||
export function devFrontendAuthPatchScript(config: UniDeskConfig): string {
|
export function devFrontendAuthPatchScript(config: UniDeskConfig): string {
|
||||||
const sshClientToken = composeRuntimeEnvValue("UNIDESK_SSH_CLIENT_TOKEN");
|
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 sshClientRouteAllowlist = composeRuntimeEnvValue("UNIDESK_SSH_CLIENT_ROUTE_ALLOWLIST") ?? "G14,G14:*,D601,D601:*";
|
||||||
const secretData = {
|
const secretData = {
|
||||||
AUTH_USERNAME: base64(config.auth.username),
|
AUTH_USERNAME: base64(config.auth.username),
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const baiduNetdiskRuntimeSecretRequirements: RuntimeSecretRequirement[] =
|
|||||||
|
|
||||||
export const baiduNetdiskAuthHealthGate: AuthHealthGate = {
|
export const baiduNetdiskAuthHealthGate: AuthHealthGate = {
|
||||||
requiredFields: ["configured", "clientIdConfigured", "clientSecretConfigured", "tokenKeyConfigured", "loggedIn"],
|
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> = {
|
export const artifactConsumerSpecs: Record<string, ArtifactConsumerSpec> = {
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export function ciHelp(): Record<string, unknown> {
|
|||||||
},
|
},
|
||||||
install: {
|
install: {
|
||||||
defaultMode: "async-job",
|
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>",
|
statusCommand: "bun scripts/cli.ts ci install-status <jobId|latest>",
|
||||||
prewarmDefault: true,
|
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.",
|
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 { chromium, type BrowserContext, type Request } from "playwright";
|
||||||
import { existsSync } from "node:fs";
|
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";
|
import { readConfig } from "./config";
|
||||||
|
|
||||||
interface CodeQueuePerfOptions {
|
interface CodeQueuePerfOptions {
|
||||||
@@ -20,6 +21,8 @@ interface ApiTiming {
|
|||||||
durationMs: number;
|
durationMs: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isCheckMode = process.execArgv.includes("--check");
|
||||||
|
|
||||||
function argValue(args: string[], name: string): string | null {
|
function argValue(args: string[], name: string): string | null {
|
||||||
const index = args.indexOf(name);
|
const index = args.indexOf(name);
|
||||||
if (index === -1) return null;
|
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>> {
|
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 fullChromePath = resolve(browsersPath, "chromium-1217/chrome-linux64/chrome");
|
||||||
const launchArgs = [
|
const launchArgs = [
|
||||||
"--no-sandbox",
|
"--no-sandbox",
|
||||||
@@ -211,6 +214,7 @@ async function runCodeQueuePerf(options: CodeQueuePerfOptions): Promise<Record<s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function main(): Promise<void> {
|
||||||
const options = readOptions();
|
const options = readOptions();
|
||||||
const result = await runCodeQueuePerf(options);
|
const result = await runCodeQueuePerf(options);
|
||||||
if (options.json) {
|
if (options.json) {
|
||||||
@@ -219,3 +223,6 @@ if (options.json) {
|
|||||||
console.log(JSON.stringify(result, null, 2));
|
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 path from "node:path";
|
||||||
import { createInterface } from "node:readline";
|
import { createInterface } from "node:readline";
|
||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
import { repoRoot } from "./config";
|
import { rootPath } from "./config";
|
||||||
import type { RenderedCliResult } from "./output";
|
import type { RenderedCliResult } from "./output";
|
||||||
|
|
||||||
type CodexTraceAction = "help" | "list" | "collect" | "show" | "grep" | "active";
|
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> --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 --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 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 trace show --session <id> [--root ~/.codex] [--tail-bytes 12000]",
|
||||||
],
|
],
|
||||||
safety: [
|
safety: [
|
||||||
@@ -97,7 +97,7 @@ export function codexTraceHelp(): Record<string, unknown> {
|
|||||||
],
|
],
|
||||||
options: {
|
options: {
|
||||||
"--root <dir>": "Codex home or another trace root. Defaults to ~/.codex.",
|
"--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}.`,
|
"--limit <n>": `Maximum included files, default ${defaultLimit}, max ${maxLimit}.`,
|
||||||
"--max-depth <n>": `Recursive scan depth, default ${defaultMaxDepth}, max ${maxDepthLimit}.`,
|
"--max-depth <n>": `Recursive scan depth, default ${defaultMaxDepth}, max ${maxDepthLimit}.`,
|
||||||
"--max-file-bytes <n|25MiB>": "Skip files larger than this during collect/list inclusion.",
|
"--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 {
|
function codexTraceCollect(options: CodexTraceOptions, candidates: CodexTraceCandidate[]): Record<string, unknown> | RenderedCliResult {
|
||||||
const selected = selectedCandidates(options, candidates);
|
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>[] = [];
|
const copied: Record<string, unknown>[] = [];
|
||||||
if (!options.dryRun) mkdirSync(outputDir, { recursive: true, mode: 0o700 });
|
if (!options.dryRun) mkdirSync(outputDir, { recursive: true, mode: 0o700 });
|
||||||
for (const candidate of selected) {
|
for (const candidate of selected) {
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ function processDiscoveryPlan(sessionId: string): Record<string, unknown> {
|
|||||||
sessionId,
|
sessionId,
|
||||||
mutation: false,
|
mutation: false,
|
||||||
signals: [
|
signals: [
|
||||||
".state/commander/sessions/<sessionId>.json",
|
"/root/.unidesk/.state/commander/sessions/<sessionId>.json",
|
||||||
"host process table filtered by executable and cwd markers",
|
"host process table filtered by executable and cwd markers",
|
||||||
"PTY/stdio bridge heartbeat file",
|
"PTY/stdio bridge heartbeat file",
|
||||||
"last prompt/trace event sequence",
|
"last prompt/trace event sequence",
|
||||||
@@ -280,7 +280,7 @@ function healthEndpointValidation(): Record<string, unknown> {
|
|||||||
function stateFileValidation(sessionId: string): Record<string, unknown> {
|
function stateFileValidation(sessionId: string): Record<string, unknown> {
|
||||||
return {
|
return {
|
||||||
surface: "state file",
|
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",
|
validationMethod: "write and read a session record only under a temporary directory during dry-run/manual inspection",
|
||||||
files: [
|
files: [
|
||||||
`sessions/${sessionId}.json`,
|
`sessions/${sessionId}.json`,
|
||||||
@@ -294,7 +294,7 @@ function stateFileValidation(sessionId: string): Record<string, unknown> {
|
|||||||
"temporary state root is deleted after the smoke contract",
|
"temporary state root is deleted after the smoke contract",
|
||||||
],
|
],
|
||||||
noRuntimeSideEffects: [
|
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 patch database state",
|
||||||
"do not discover or signal live host Codex processes",
|
"do not discover or signal live host Codex processes",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { existsSync, readFileSync } from "node:fs";
|
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";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
export interface UniDeskConfig {
|
export interface UniDeskConfig {
|
||||||
@@ -101,8 +102,14 @@ export interface UniDeskMicroserviceConfig {
|
|||||||
|
|
||||||
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
||||||
export const repoRoot = join(moduleDir, "..", "..");
|
export const repoRoot = join(moduleDir, "..", "..");
|
||||||
|
export const stateRoot = join(homedir(), ".unidesk", ".state");
|
||||||
|
|
||||||
export function rootPath(...parts: string[]): string {
|
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);
|
return join(repoRoot, ...parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import { k8sKubeconfig, nativeK3sCtrAddress, nativeK3sImage, nativeK3sInstallVer
|
|||||||
|
|
||||||
export function syncDevFrontendAuthScript(config: UniDeskConfig): string {
|
export function syncDevFrontendAuthScript(config: UniDeskConfig): string {
|
||||||
const sshClientToken = composeRuntimeEnvValue("UNIDESK_SSH_CLIENT_TOKEN");
|
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 sshClientRouteAllowlist = composeRuntimeEnvValue("UNIDESK_SSH_CLIENT_ROUTE_ALLOWLIST") ?? "G14,G14:*,D601,D601:*";
|
||||||
const data = {
|
const data = {
|
||||||
AUTH_USERNAME: Buffer.from(config.auth.username, "utf8").toString("base64"),
|
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;
|
if (previous.length > 0 && previous !== legacyDefault) return previous;
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
};
|
};
|
||||||
let logRoot: string;
|
const logRoot = resolve(rootPath(config.paths.logsDir));
|
||||||
let logDay: string;
|
let logDay: string;
|
||||||
let logPrefix: string;
|
let logPrefix: string;
|
||||||
if (!freshLogPrefix && previousRaw.length > 0) {
|
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;
|
logPrefix = previousValue("UNIDESK_LOG_PREFIX") || localDateParts(new Date()).stamp;
|
||||||
logDay = previousValue("UNIDESK_LOG_DAY") || logPrefix.match(/^\d{8}/u)?.[0] || localDateParts(new Date()).day;
|
logDay = previousValue("UNIDESK_LOG_DAY") || logPrefix.match(/^\d{8}/u)?.[0] || localDateParts(new Date()).day;
|
||||||
} else {
|
} else {
|
||||||
const parts = localDateParts(new Date());
|
const parts = localDateParts(new Date());
|
||||||
logRoot = resolve(rootPath(config.paths.logsDir));
|
|
||||||
logDay = parts.day;
|
logDay = parts.day;
|
||||||
logPrefix = parts.stamp;
|
logPrefix = parts.stamp;
|
||||||
}
|
}
|
||||||
logRoot = resolve(logRoot);
|
|
||||||
mkdirSync(join(logRoot, logDay), { recursive: true });
|
mkdirSync(join(logRoot, logDay), { recursive: true });
|
||||||
chmodSync(logRoot, 0o777);
|
chmodSync(logRoot, 0o777);
|
||||||
chmodSync(join(logRoot, logDay), 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 { 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 { 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";
|
import { runRemoteGcCommand } from "./gc-remote";
|
||||||
|
|
||||||
type GcRisk = "low" | "medium" | "high" | "blocked";
|
type GcRisk = "low" | "medium" | "high" | "blocked";
|
||||||
@@ -928,11 +928,12 @@ function yamlStateRoots(value: unknown, label: string): GcPathRoot[] {
|
|||||||
return Object.entries(record).map(([id, rawPath]) => {
|
return Object.entries(record).map(([id, rawPath]) => {
|
||||||
if (!/^[a-z0-9._-]+$/iu.test(id)) throw new Error(`${label}.${id} must use a simple id`);
|
if (!/^[a-z0-9._-]+$/iu.test(id)) throw new Error(`${label}.${id} must use a simple id`);
|
||||||
const displayPath = yamlString(rawPath, `${label}.${id}`);
|
const displayPath = yamlString(rawPath, `${label}.${id}`);
|
||||||
if (displayPath.startsWith("/") || displayPath.includes("..") || displayPath.includes("\\")) {
|
if (displayPath.includes("..") || displayPath.includes("\\")) {
|
||||||
throw new Error(`${label}.${id} must be a repo-relative .state path without ..`);
|
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/`);
|
const path = resolvePath(displayPath);
|
||||||
return { id, displayPath, path: rootPath(...displayPath.split("/")) };
|
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 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 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: "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 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 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 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." },
|
{ 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",
|
"--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",
|
"--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",
|
"--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-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",
|
"--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",
|
"--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> --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 --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 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 trace show --session <id> [--root ~/.codex] [--tail-bytes 12000]",
|
||||||
"bun scripts/cli.ts codex deploy <commitId> # disabled legacy deployment entry",
|
"bun scripts/cli.ts codex deploy <commitId> # disabled legacy deployment entry",
|
||||||
"bun scripts/cli.ts agentrun get tasks --queue commander --limit 20",
|
"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.",
|
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.",
|
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.",
|
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.",
|
note: "Use --root <dir> to scan another local Codex trace directory; collect copies bounded files locally and never uploads or deletes source files.",
|
||||||
},
|
},
|
||||||
unreadTriage: {
|
unreadTriage: {
|
||||||
@@ -544,7 +544,7 @@ function jobHelp(): unknown {
|
|||||||
"bun scripts/cli.ts jobs get <jobId|latest> [--tail-bytes N]",
|
"bun scripts/cli.ts jobs get <jobId|latest> [--tail-bytes N]",
|
||||||
"bun scripts/cli.ts job cancel <jobId>",
|
"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: {
|
actions: {
|
||||||
plan: "Read YAML configRefs and show local source/materialization and target k3s objects without mutation.",
|
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.",
|
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.",
|
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.",
|
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 {
|
function displayPath(path: string): string {
|
||||||
if (path.startsWith(`${repoRoot}/`)) return path.slice(repoRoot.length + 1);
|
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;
|
return path;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,11 +321,7 @@ export function monitorWebBuildkitStatePlan(cicd: Record<string, unknown>): Reco
|
|||||||
|
|
||||||
export function secretSourcePaths(sourceRef: string): string[] {
|
export function secretSourcePaths(sourceRef: string): string[] {
|
||||||
if (isAbsolute(sourceRef)) return [sourceRef];
|
if (isAbsolute(sourceRef)) return [sourceRef];
|
||||||
const paths = [join(repoRoot, ".state", "secrets", sourceRef)];
|
return [rootPath(".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)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseEnvFile(textValue: string): Record<string, string> {
|
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 sourceKey = stringAt(source, "sourceKey");
|
||||||
const sourceLine = numberAtNullable(source, "sourceLine");
|
const sourceLine = numberAtNullable(source, "sourceLine");
|
||||||
const paths = secretSourcePaths(sourceRef);
|
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 };
|
if (!existsSync(sourcePath)) return { ok: false, error: "secret-source-missing", sourceRef, sourceKey, sourcePath: displayPath(sourcePath), valuesRedacted: true };
|
||||||
const textValue = readFileSync(sourcePath, "utf8");
|
const textValue = readFileSync(sourcePath, "utf8");
|
||||||
const value = sourceLine === null ? parseEnvFile(textValue)[sourceKey] : textValue.split(/\r?\n/u)[sourceLine - 1]?.replace(/\r$/u, "");
|
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");
|
const sourceLine = numberAtNullable(source, "usernameSourceLine");
|
||||||
if (sourceRef === null || sourceLine === null) return { ok: false, error: "web-account-json-username-missing" };
|
if (sourceRef === null || sourceLine === null) return { ok: false, error: "web-account-json-username-missing" };
|
||||||
const paths = secretSourcePaths(sourceRef);
|
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" };
|
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, "") ?? "";
|
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 };
|
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 sourceRef = stringAt(state.publicExposure, "frpc.tokenSourceRef");
|
||||||
const sourceKey = stringAt(state.publicExposure, "frpc.tokenSourceKey");
|
const sourceKey = stringAt(state.publicExposure, "frpc.tokenSourceKey");
|
||||||
const paths = secretSourcePaths(sourceRef);
|
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 };
|
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 values = parseEnvFile(readFileSync(sourcePath, "utf8"));
|
||||||
const token = values[sourceKey];
|
const token = values[sourceKey];
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
// Responsibility: Quick-verify observe orchestration and artifact interpretation for web-probe sentinel P5 validation.
|
// Responsibility: Quick-verify observe orchestration and artifact interpretation for web-probe sentinel P5 validation.
|
||||||
import { createHash, randomUUID } from "node:crypto";
|
import { createHash, randomUUID } from "node:crypto";
|
||||||
import { existsSync, readFileSync } from "node:fs";
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
|
||||||
import type { CommandResult } from "./command";
|
import type { CommandResult } from "./command";
|
||||||
import { runCommand } from "./command";
|
import { runCommand } from "./command";
|
||||||
import { resolveCliChildJsonCommandResult } from "./cli-child-json-recovery";
|
import { resolveCliChildJsonCommandResult } from "./cli-child-json-recovery";
|
||||||
@@ -1959,7 +1958,7 @@ function readPromptSetForScenario(state: SentinelCicdState, scenario: Record<str
|
|||||||
const sourceRef = stringAt(promptSet, "promptSourceRef");
|
const sourceRef = stringAt(promptSet, "promptSourceRef");
|
||||||
const key = stringAt(promptSet, "promptSourceKey");
|
const key = stringAt(promptSet, "promptSourceKey");
|
||||||
const paths = secretSourcePaths(sourceRef);
|
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 summary = { sourceRef, sourceKey: key, sourcePath: displayPath(sourcePath), valuesRedacted: true };
|
||||||
const runtimeRaw = process.env[key];
|
const runtimeRaw = process.env[key];
|
||||||
const values = existsSync(sourcePath) ? parseEnvFile(readFileSync(sourcePath, "utf8")) : {};
|
const values = existsSync(sourcePath) ? parseEnvFile(readFileSync(sourcePath, "utf8")) : {};
|
||||||
|
|||||||
@@ -493,7 +493,7 @@ export function runNodePublicExposure(options: NodePublicExposureOptions): Recor
|
|||||||
source,
|
source,
|
||||||
mutation: false,
|
mutation: false,
|
||||||
valuesRedacted: true,
|
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);
|
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 } {
|
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 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" };
|
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 values = parseEnvFile(readFileSync(path, "utf8"));
|
||||||
const value = values[exposure.tokenSourceKey];
|
const value = values[exposure.tokenSourceKey];
|
||||||
@@ -597,11 +597,7 @@ export function readPublicExposureTokenSource(exposure: HwlabRuntimePublicExposu
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function publicExposureTokenSourcePaths(exposure: HwlabRuntimePublicExposureSpec): string[] {
|
export function publicExposureTokenSourcePaths(exposure: HwlabRuntimePublicExposureSpec): string[] {
|
||||||
const paths = [join(repoRoot, ".state", "secrets", exposure.tokenSourceRef)];
|
return [rootPath(".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)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function publicExposureSecretStatus(fields: Record<string, string>, result: CommandResult): Record<string, unknown> {
|
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[] {
|
export function localSecretSourcePaths(sourceRef: string): string[] {
|
||||||
if (isAbsolute(sourceRef)) return [sourceRef];
|
if (isAbsolute(sourceRef)) return [sourceRef];
|
||||||
const marker = "/.worktree/";
|
return [rootPath(".state", "secrets", sourceRef)];
|
||||||
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)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shortSecretFingerprint(value: string): string {
|
export function shortSecretFingerprint(value: string): string {
|
||||||
@@ -1274,7 +1266,7 @@ export function externalPostgresSecretSetupScript(spec: HwlabRuntimeLaneSpec, dr
|
|||||||
|
|
||||||
export function externalPostgresSecretSourceRoot(spec: HwlabRuntimeLaneSpec): string {
|
export function externalPostgresSecretSourceRoot(spec: HwlabRuntimeLaneSpec): string {
|
||||||
const configRef = spec.externalPostgres?.configRef;
|
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 configPath = rootPath(configRef);
|
||||||
const parsed = Bun.YAML.parse(readFileSync(configPath, "utf8")) as unknown;
|
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`);
|
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[] {
|
export function secretSourcePaths(sourceRef: string): string[] {
|
||||||
if (isAbsolute(sourceRef)) return [sourceRef];
|
if (isAbsolute(sourceRef)) return [sourceRef];
|
||||||
const paths = [join(repoRoot, ".state", "secrets", sourceRef)];
|
return [rootPath(".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)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function displayRepoPath(path: string): string {
|
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 } {
|
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 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];
|
const runtimeValue = process.env[sourceKey];
|
||||||
if (!existsSync(sourcePath)) {
|
if (!existsSync(sourcePath)) {
|
||||||
if (runtimeValue !== undefined && runtimeValue.length > 0) return { ok: true, value: runtimeValue, sourcePath, sourcePresent: false };
|
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 { existsSync, readFileSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { runCommand } from "./command";
|
import { runCommand } from "./command";
|
||||||
import { type UniDeskConfig, repoRoot } from "./config";
|
import { type UniDeskConfig, repoRoot, rootPath } from "./config";
|
||||||
import { jsonByteLength, previewJson } from "./preview";
|
import { jsonByteLength, previewJson } from "./preview";
|
||||||
|
|
||||||
// Todo Note misleading-404 rewrite (issue #198) for legacy deployments.
|
// 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 });
|
const result = runCommand(command, repoRoot, { timeoutMs: init?.timeoutMs });
|
||||||
if (result.exitCode !== 0) {
|
if (result.exitCode !== 0) {
|
||||||
if (backendCoreContainerMissing(result.stderr)) {
|
if (backendCoreContainerMissing(result.stderr)) {
|
||||||
const envPath = join(repoRoot, ".state", "docker-compose.env");
|
const envPath = rootPath(".state", "docker-compose.env");
|
||||||
return backendCoreUnavailableDiagnostic({
|
return backendCoreUnavailableDiagnostic({
|
||||||
exitCode: result.exitCode,
|
exitCode: result.exitCode,
|
||||||
stdoutTail: result.stdout.slice(-1200),
|
stdoutTail: result.stdout.slice(-1200),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|||||||
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
import { dirname } from "node:path";
|
import { dirname } from "node:path";
|
||||||
import type { UniDeskConfig } from "./config";
|
import type { UniDeskConfig } from "./config";
|
||||||
import { rootPath } from "./config";
|
import { rootPath, stateRoot } from "./config";
|
||||||
import { runCommand, type CommandResult } from "./command";
|
import { runCommand, type CommandResult } from "./command";
|
||||||
import { resolveEgressProxySourceRef, type MasterShadowsocksSourceSpec } from "./egress-proxy-sources";
|
import { resolveEgressProxySourceRef, type MasterShadowsocksSourceSpec } from "./egress-proxy-sources";
|
||||||
import type { RenderedCliResult } from "./output";
|
import type { RenderedCliResult } from "./output";
|
||||||
@@ -332,11 +332,11 @@ function managedClientSpec(raw: Record<string, unknown>, label: string): HostPro
|
|||||||
mode,
|
mode,
|
||||||
upstreamUrl: httpsUrlField(raw, "upstreamUrl", label),
|
upstreamUrl: httpsUrlField(raw, "upstreamUrl", label),
|
||||||
version: stringField(raw, "version", label),
|
version: stringField(raw, "version", label),
|
||||||
archiveCachePath: repoStatePathField(raw, "archiveCachePath", label),
|
archiveCachePath: canonicalStatePathField(raw, "archiveCachePath", label),
|
||||||
archiveSha256: sha256Field(raw, "archiveSha256", label),
|
archiveSha256: sha256Field(raw, "archiveSha256", label),
|
||||||
archiveInstallPath: absolutePathField(raw, "archiveInstallPath", label),
|
archiveInstallPath: absolutePathField(raw, "archiveInstallPath", label),
|
||||||
binaryMember: relativePathField(raw, "binaryMember", label),
|
binaryMember: relativePathField(raw, "binaryMember", label),
|
||||||
binaryCachePath: repoStatePathField(raw, "binaryCachePath", label),
|
binaryCachePath: canonicalStatePathField(raw, "binaryCachePath", label),
|
||||||
binarySha256: sha256Field(raw, "binarySha256", label),
|
binarySha256: sha256Field(raw, "binarySha256", label),
|
||||||
installPath: absolutePathField(raw, "installPath", label),
|
installPath: absolutePathField(raw, "installPath", label),
|
||||||
configPath: absolutePathField(raw, "configPath", label),
|
configPath: absolutePathField(raw, "configPath", label),
|
||||||
@@ -1314,10 +1314,10 @@ function repoYamlPathField(obj: Record<string, unknown>, key: string, label: str
|
|||||||
return value;
|
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);
|
const value = stringField(obj, key, label);
|
||||||
if (value.startsWith("/") || value.includes("..") || !value.startsWith(".state/")) {
|
if (value.includes("..") || !value.startsWith(`${stateRoot}/`)) {
|
||||||
throw new Error(`${label}.${key} must be a repo-relative .state/ path without ..`);
|
throw new Error(`${label}.${key} must be under ${stateRoot}/ without ..`);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
||||||
import { dirname, join, resolve } from "node:path";
|
import { dirname, join, resolve } from "node:path";
|
||||||
import { chromium, firefox, webkit, type Browser, type BrowserContext, type BrowserType, type Page } from "playwright";
|
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";
|
import { emitError, emitJson } from "./output";
|
||||||
|
|
||||||
type BrowserName = "chromium" | "firefox" | "webkit";
|
type BrowserName = "chromium" | "firefox" | "webkit";
|
||||||
@@ -22,7 +22,7 @@ interface ParsedOptions {
|
|||||||
fullPage: boolean;
|
fullPage: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateRoot = join(repoRoot, ".state", "playwright-cli");
|
const stateRoot = rootPath(".state", "playwright-cli");
|
||||||
const defaultScreenshotPath = join(stateRoot, "screenshots", "latest.png");
|
const defaultScreenshotPath = join(stateRoot, "screenshots", "latest.png");
|
||||||
const supportedCommands = ["open", "screenshot", "eval", "session-list", "session-delete"];
|
const supportedCommands = ["open", "screenshot", "eval", "session-list", "session-delete"];
|
||||||
const unsupportedInteractiveCommands = new Set([
|
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 envFile = optionValue(args, "--env-file") ?? rootPath(".state", `provider-${providerId}.env`);
|
||||||
const composeFile = optionValue(args, "--compose-file") ?? rootPath(`provider-${providerId}.yml`);
|
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 {
|
return {
|
||||||
providerId,
|
providerId,
|
||||||
masterServer: optionValue(args, "--master-server") ?? optionValue(args, "--master") ?? defaultMasterServer(config),
|
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_HOST_PROJECT_ROOT=${repoRoot}`,
|
||||||
"PROVIDER_UPGRADE_WORKSPACE_PATH=/workspace",
|
"PROVIDER_UPGRADE_WORKSPACE_PATH=/workspace",
|
||||||
`PROVIDER_UPGRADE_COMPOSE_FILE=provider-${options.providerId}.yml`,
|
`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_COMPOSE_PROJECT=unidesk-${slug}`,
|
||||||
"PROVIDER_UPGRADE_SERVICE=provider-gateway",
|
"PROVIDER_UPGRADE_SERVICE=provider-gateway",
|
||||||
`PROVIDER_UPGRADE_RUNNER_IMAGE=unidesk_provider-gateway:${slug}`,
|
`PROVIDER_UPGRADE_RUNNER_IMAGE=unidesk_provider-gateway:${slug}`,
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ pub async fn codex_queue_load_test(
|
|||||||
chrono::Utc::now().timestamp_millis(),
|
chrono::Utc::now().timestamp_millis(),
|
||||||
rand::random::<u32>()
|
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 output_path = format!("{dir}/{run_id}.json");
|
||||||
let stderr_path = format!("{dir}/{run_id}.stderr");
|
let stderr_path = format!("{dir}/{run_id}.stderr");
|
||||||
let exit_path = format!("{dir}/{run_id}.exit");
|
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)))
|
.map(|value| format!(" --url {}", shell_quote(value)))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let start_command = format!(
|
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(dir),
|
||||||
shell_quote(&output_path),
|
shell_quote(&output_path),
|
||||||
shell_quote(&stderr_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 timeoutMs = numberFromUnknown(body.timeoutMs, 90_000, 5_000, 180_000);
|
||||||
const targetMs = numberFromUnknown(body.targetMs, 1_000, 100, 60_000);
|
const targetMs = numberFromUnknown(body.targetMs, 1_000, 100, 60_000);
|
||||||
const runId = safePerfRunId();
|
const runId = safePerfRunId();
|
||||||
const dir = ".state/code-queue-perf";
|
const dir = "/root/.unidesk/.state/code-queue-perf";
|
||||||
const browsersPath = ".state/playwright-browsers";
|
const browsersPath = "/root/.unidesk/.state/playwright-browsers";
|
||||||
const outputPath = `${dir}/${runId}.json`;
|
const outputPath = `${dir}/${runId}.json`;
|
||||||
const stderrPath = `${dir}/${runId}.stderr`;
|
const stderrPath = `${dir}/${runId}.stderr`;
|
||||||
const exitPath = `${dir}/${runId}.exit`;
|
const exitPath = `${dir}/${runId}.exit`;
|
||||||
|
|||||||
@@ -253,11 +253,11 @@ spec:
|
|||||||
type: Directory
|
type: Directory
|
||||||
- name: logs
|
- name: logs
|
||||||
hostPath:
|
hostPath:
|
||||||
path: /root/unidesk/.state/code-queue/logs
|
path: /root/.unidesk/.state/code-queue/logs
|
||||||
type: DirectoryOrCreate
|
type: DirectoryOrCreate
|
||||||
- name: state
|
- name: state
|
||||||
hostPath:
|
hostPath:
|
||||||
path: /root/unidesk/.state/code-queue
|
path: /root/.unidesk/.state/code-queue
|
||||||
type: DirectoryOrCreate
|
type: DirectoryOrCreate
|
||||||
---
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
@@ -505,11 +505,11 @@ spec:
|
|||||||
type: Directory
|
type: Directory
|
||||||
- name: logs
|
- name: logs
|
||||||
hostPath:
|
hostPath:
|
||||||
path: /root/unidesk/.state/code-queue/logs
|
path: /root/.unidesk/.state/code-queue/logs
|
||||||
type: DirectoryOrCreate
|
type: DirectoryOrCreate
|
||||||
- name: state
|
- name: state
|
||||||
hostPath:
|
hostPath:
|
||||||
path: /root/unidesk/.state/code-queue
|
path: /root/.unidesk/.state/code-queue
|
||||||
type: DirectoryOrCreate
|
type: DirectoryOrCreate
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
@@ -1219,11 +1219,11 @@ spec:
|
|||||||
type: Directory
|
type: Directory
|
||||||
- name: logs
|
- name: logs
|
||||||
hostPath:
|
hostPath:
|
||||||
path: /root/unidesk/.state/code-queue/logs
|
path: /root/.unidesk/.state/code-queue/logs
|
||||||
type: DirectoryOrCreate
|
type: DirectoryOrCreate
|
||||||
- name: state
|
- name: state
|
||||||
hostPath:
|
hostPath:
|
||||||
path: /root/unidesk/.state/code-queue
|
path: /root/.unidesk/.state/code-queue
|
||||||
type: DirectoryOrCreate
|
type: DirectoryOrCreate
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
|
|||||||
Reference in New Issue
Block a user