@moondeck/station 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +149 -2
- package/bin/uai-host.mjs +130 -0
- package/db/migrations/0000_host_tasks.sql +12 -0
- package/db/migrations/0001_host_ui.sql +11 -0
- package/db/migrations/0002_host_github_tokens.sql +8 -0
- package/db/migrations/0003_host_ssh_keys.sql +8 -0
- package/db/migrations/0004_host_owner_name.sql +1 -0
- package/db/migrations/0005_host_env_vars.sql +5 -0
- package/db/migrations/0006_host_project_env.sql +9 -0
- package/db/migrations/0007_host_github_token_kind.sql +7 -0
- package/db/migrations/0008_host_mcp_connections.sql +18 -0
- package/db/migrations/0009_host_agent_sessions.sql +15 -0
- package/db/migrations/0010_host_engine_accounts.sql +13 -0
- package/db/migrations/0011_codex_thread_id.sql +5 -0
- package/db/migrations/0012_agent_session_attach_key.sql +4 -0
- package/db/migrations/0013_github_credential_generations.sql +11 -0
- package/db/migrations/0014_host_inventory_event_index.sql +1 -0
- package/db/migrations/0015_host_settings.sql +9 -0
- package/db/migrations/0016_task_environment.sql +2 -0
- package/db/migrations/0017_engine_account_scope.sql +7 -0
- package/db/migrations/0018_engine_account_position.sql +3 -0
- package/db/migrations/0019_engine_account_store_ref.sql +3 -0
- package/db/migrations/0020_user_credential_store_ref.sql +6 -0
- package/db/migrations/0021_project_env_store_ref.sql +3 -0
- package/db/migrations/0022_task_project_ids.sql +5 -0
- package/db/migrations/0023_project_env_org.sql +4 -0
- package/db/migrations/0024_task_env_mode.sql +9 -0
- package/db/migrations/0025_project_env_authority.sql +147 -0
- package/db/migrations/0026_project_env_replica.sql +150 -0
- package/db/migrations/0027_cloud_credential_foundation.sql +51 -0
- package/db/migrations/0028_leased_engine_runtimes.sql +11 -0
- package/db/migrations/0029_task_shared_files_mode.sql +5 -0
- package/db/migrations/0030_credential_cleanup_restops.sql +12 -0
- package/db/migrations/meta/_journal.json +223 -0
- package/db/schema.ts +850 -0
- package/images/standard/Dockerfile +347 -0
- package/images/standard/README.md +167 -0
- package/images/standard/container/code-server-settings.json +63 -0
- package/images/standard/container/corepack-version +1 -0
- package/images/standard/container/uai-init +704 -0
- package/images/standard/container/uai-materialize-runtimes +1527 -0
- package/images/standard/container/uai-task-forwarder.mjs +303 -0
- package/images/standard/tool-versions +2 -0
- package/lib/__fixtures__/skill-index/SKILL.md +10 -0
- package/lib/__fixtures__/skill-index/skills/accessibility-checklist.md +7 -0
- package/lib/__fixtures__/skill-index/skills/brand-voice.md +9 -0
- package/lib/__fixtures__/skill-index/skills/design-tokens.md +9 -0
- package/lib/agent-cli.ts +1507 -0
- package/lib/agent-errors.ts +22 -0
- package/lib/agent.ts +672 -0
- package/lib/agents/attach-compatibility.ts +51 -0
- package/lib/agents/claude.ts +918 -0
- package/lib/agents/codex-token-usage.fixture.json +130 -0
- package/lib/agents/codex.ts +1392 -0
- package/lib/agents/cursor-period-usage.ts +253 -0
- package/lib/agents/cursor.ts +562 -0
- package/lib/agents/dispatch.ts +78 -0
- package/lib/agents/durable-proc.ts +334 -0
- package/lib/agents/factory.ts +57 -0
- package/lib/agents/grok.ts +381 -0
- package/lib/agents/kimi.ts +329 -0
- package/lib/agents/machine-durable.ts +604 -0
- package/lib/agents/mock.ts +133 -0
- package/lib/agents/mode.ts +64 -0
- package/lib/agents/opencode.ts +508 -0
- package/lib/agents/output-source.ts +3 -0
- package/lib/agents/proc.ts +221 -0
- package/lib/agents/rate-limit.ts +337 -0
- package/lib/agents/registry.ts +182 -0
- package/lib/agents/session-state.ts +197 -0
- package/lib/agents/skill-index-preamble.fixture.ts +26 -0
- package/lib/agents/transport.ts +741 -0
- package/lib/agents/types.ts +254 -0
- package/lib/agents/uai.ts +106 -0
- package/lib/agents/usage.ts +44 -0
- package/lib/apple-one-off.ts +277 -0
- package/lib/apple-runtime-recycle.ts +237 -0
- package/lib/apple-uninstall-teardown.ts +224 -0
- package/lib/assignment-evidence.ts +94 -0
- package/lib/attachments.ts +48 -0
- package/lib/browser-testing.ts +1537 -0
- package/lib/capture-flow.ts +332 -0
- package/lib/cloud-credential-cleanup-adapter.ts +123 -0
- package/lib/cloud-credential-host.ts +512 -0
- package/lib/cloud-credential-leases.ts +2587 -0
- package/lib/cloud-credential-runtime.ts +1357 -0
- package/lib/cloud-state.ts +56 -0
- package/lib/codex-auth.ts +456 -0
- package/lib/command-db.ts +310 -0
- package/lib/compose-network-recovery.ts +283 -0
- package/lib/connection-secret-redaction.ts +25 -0
- package/lib/container-runtime.ts +1453 -0
- package/lib/credential-cleanup-inventory.ts +490 -0
- package/lib/credential-consumers/engine-anchor.ts +1880 -0
- package/lib/credential-consumers/engines.ts +797 -0
- package/lib/credential-consumers/files.ts +934 -0
- package/lib/credential-consumers/github.ts +702 -0
- package/lib/credential-consumers/mcp.ts +389 -0
- package/lib/credential-consumers/task-files.ts +127 -0
- package/lib/credential-consumers/task-secrets.ts +10 -0
- package/lib/credential-store-pickup.ts +149 -0
- package/lib/credential-store-scope.ts +112 -0
- package/lib/credential-store.ts +932 -0
- package/lib/credentials-activation-authority.ts +709 -0
- package/lib/credentials-purge.ts +1006 -0
- package/lib/custom-connection-capture.ts +395 -0
- package/lib/custom-connection-launch.ts +384 -0
- package/lib/custom-connection-runtime.ts +141 -0
- package/lib/db.ts +80 -0
- package/lib/disk-pressure.ts +42 -0
- package/lib/docker-exec.ts +156 -0
- package/lib/engine-accounts.ts +1970 -0
- package/lib/engine-connect.ts +321 -0
- package/lib/engine-credential-env.ts +52 -0
- package/lib/engine-login.ts +2933 -0
- package/lib/engines.ts +1342 -0
- package/lib/enrollment-state.ts +251 -0
- package/lib/env-file.ts +246 -0
- package/lib/env-schema.ts +891 -0
- package/lib/env.ts +195 -0
- package/lib/fs-aliases.ts +49 -0
- package/lib/git-clone-credentials.ts +334 -0
- package/lib/git-diff.ts +530 -0
- package/lib/git-identity.ts +715 -0
- package/lib/github-git-auth.ts +207 -0
- package/lib/github-origin.ts +28 -0
- package/lib/github-tokens.ts +2439 -0
- package/lib/host-cloud-url.ts +62 -0
- package/lib/host-config.ts +279 -0
- package/lib/host-env.ts +424 -0
- package/lib/host-logs.ts +965 -0
- package/lib/internal-connections.ts +5 -0
- package/lib/keyed-promise-tail.ts +23 -0
- package/lib/launch-mode.ts +25 -0
- package/lib/lease.ts +239 -0
- package/lib/legacy-host.ts +769 -0
- package/lib/legacy-runtime-retirement.ts +424 -0
- package/lib/legacy-runtime-v1.fixture.ts +627 -0
- package/lib/machine-exec.ts +238 -0
- package/lib/machine-gateway-tunnel.ts +344 -0
- package/lib/machine-keys.ts +65 -0
- package/lib/machine-project-env-loader.ts +152 -0
- package/lib/machine-provider-aws.ts +923 -0
- package/lib/machine-provider-local.ts +338 -0
- package/lib/machine-provider.ts +90 -0
- package/lib/managed-activation-watcher.ts +72 -0
- package/lib/managed-install-owner-watcher.ts +55 -0
- package/lib/managed-operation-drain.ts +57 -0
- package/lib/managed-runtime.ts +4296 -0
- package/lib/managed-update-scheduler.ts +125 -0
- package/lib/mcp-capture.ts +865 -0
- package/lib/mcp-config-lock.ts +2 -0
- package/lib/mcp-connections.ts +959 -0
- package/lib/mcp-gateway.ts +2594 -0
- package/lib/obs.ts +512 -0
- package/lib/openrouter-budget.ts +247 -0
- package/lib/orchestrator.ts +11409 -0
- package/lib/person-capture.ts +327 -0
- package/lib/preview-ports.ts +85 -0
- package/lib/preview-sidecar.ts +331 -0
- package/lib/project-env-authority.ts +1319 -0
- package/lib/project-env-feature.ts +74 -0
- package/lib/project-env-legacy-tags.ts +130 -0
- package/lib/project-env-loader.ts +242 -0
- package/lib/project-env-replica.ts +2094 -0
- package/lib/project-env-schema-capture.ts +342 -0
- package/lib/project-env-staging.ts +148 -0
- package/lib/project-env-task-policy.ts +176 -0
- package/lib/project-env-task-replica.ts +1752 -0
- package/lib/release-feed.ts +61 -0
- package/lib/release-manifest.ts +710 -0
- package/lib/release-trust.ts +46 -0
- package/lib/repo-clone.ts +38 -0
- package/lib/routing/shadow.ts +261 -0
- package/lib/runner-boot.ts +10 -0
- package/lib/runtime-activation-tail.ts +279 -0
- package/lib/runtime-archive.ts +1216 -0
- package/lib/runtime-authority.ts +166 -0
- package/lib/runtime-guard.ts +36 -0
- package/lib/runtime-provider-state.ts +169 -0
- package/lib/runtime-reconnect.ts +86 -0
- package/lib/runtime-state.ts +1033 -0
- package/lib/safe-http.ts +364 -0
- package/lib/secrets-proxy/address.ts +95 -0
- package/lib/secrets-proxy/audit.ts +30 -0
- package/lib/secrets-proxy/ca-store.ts +200 -0
- package/lib/secrets-proxy/ca.ts +470 -0
- package/lib/secrets-proxy/container-steps.ts +138 -0
- package/lib/secrets-proxy/der.ts +273 -0
- package/lib/secrets-proxy/engine.ts +1633 -0
- package/lib/secrets-proxy/host.ts +93 -0
- package/lib/secrets-proxy/hostname.ts +57 -0
- package/lib/secrets-proxy/placeholders.ts +196 -0
- package/lib/secrets-proxy/policy.ts +150 -0
- package/lib/secrets-proxy/readiness.ts +148 -0
- package/lib/secrets-proxy/runtime.ts +582 -0
- package/lib/secrets-proxy/scrub.ts +559 -0
- package/lib/secrets-proxy/sidecar-view.ts +139 -0
- package/lib/secrets-proxy/task-scripts.ts +262 -0
- package/lib/secrets-proxy/task-trust.ts +605 -0
- package/lib/secrets-proxy/trust-watch.ts +80 -0
- package/lib/secrets.ts +71 -0
- package/lib/shared-files-s3.ts +976 -0
- package/lib/shared-files.ts +156 -0
- package/lib/sidecar-assign.ts +49 -0
- package/lib/sidecar-guard.ts +58 -0
- package/lib/sidecar-resume.ts +178 -0
- package/lib/sidecar.ts +69 -0
- package/lib/signin-box-credentials.ts +465 -0
- package/lib/signin-box.ts +114 -0
- package/lib/skill-index-example.fixture.ts +56 -0
- package/lib/skill-layout.ts +417 -0
- package/lib/skills.ts +489 -0
- package/lib/ssh.ts +401 -0
- package/lib/standard-image-cleanup.ts +117 -0
- package/lib/standard-image-pin.ts +207 -0
- package/lib/standard-image.ts +1697 -0
- package/lib/stopped-task-status-queue.ts +120 -0
- package/lib/task-container-cli.ts +269 -0
- package/lib/task-credential-cleanup.ts +752 -0
- package/lib/task-diff-sandbox.ts +464 -0
- package/lib/task-diff.ts +91 -0
- package/lib/task-env-forwarder.ts +228 -0
- package/lib/task-env-source.ts +249 -0
- package/lib/task-environment/anchor.ts +31 -0
- package/lib/task-environment/apple-container.ts +860 -0
- package/lib/task-environment/docker.ts +1344 -0
- package/lib/task-environment/index.ts +466 -0
- package/lib/task-environment/legacy-adoption.ts +467 -0
- package/lib/task-environment/machine-environment.ts +61 -0
- package/lib/task-environment/machine-task-up.ts +1227 -0
- package/lib/task-environment/machine.ts +974 -0
- package/lib/task-environment/registry.ts +59 -0
- package/lib/task-environment/self-identity.ts +20 -0
- package/lib/task-environment/self.ts +566 -0
- package/lib/task-environment/types.ts +642 -0
- package/lib/task-environment/workspace-files.ts +685 -0
- package/lib/task-environment/workspace-runtime.fixture.ts +50 -0
- package/lib/task-files-machine-retirement-outbox.ts +592 -0
- package/lib/task-files-machine-retirement.ts +116 -0
- package/lib/task-files-readiness.ts +127 -0
- package/lib/task-gateway-port.ts +22 -0
- package/lib/task-gateway-protocol.ts +190 -0
- package/lib/task-gateway-recovery.ts +47 -0
- package/lib/task-gateway-state.ts +512 -0
- package/lib/task-identity.ts +19 -0
- package/lib/task-inventory.ts +682 -0
- package/lib/task-machine-credentials-activation.ts +95 -0
- package/lib/task-machine-swap-safety.ts +729 -0
- package/lib/task-person-credentials.ts +76 -0
- package/lib/task-project-ops-host.ts +147 -0
- package/lib/task-project-ops.ts +1062 -0
- package/lib/task-secret-staging.ts +16 -0
- package/lib/task-status.ts +46 -0
- package/lib/task-token.ts +46 -0
- package/lib/token-broker-adopt.ts +344 -0
- package/lib/token-broker-lambda.ts +105 -0
- package/lib/token-broker-local.ts +392 -0
- package/lib/token-broker-revision.ts +310 -0
- package/lib/token-broker-ssm.ts +525 -0
- package/lib/token-broker.ts +218 -0
- package/lib/token-lanes/openai-oauth.ts +123 -0
- package/lib/token-lanes/registry.ts +45 -0
- package/lib/token-lanes/types.ts +163 -0
- package/lib/token-lanes/xai-oauth.ts +165 -0
- package/lib/transcript.ts +128 -0
- package/lib/tunnel-registry.ts +220 -0
- package/lib/tunnel-runtime.ts +235 -0
- package/lib/ulid.ts +16 -0
- package/lib/user-bus-env.ts +116 -0
- package/lib/user-credentials-store.ts +435 -0
- package/lib/vnode-pressure.ts +142 -0
- package/package.json +131 -4
- package/runner/runner.mjs +442 -0
- package/scripts/agent/_common.sh +633 -0
- package/scripts/agent/env-hook.sh +626 -0
- package/scripts/agent/moondeck-cli.sh +5 -0
- package/scripts/agent/task-down.sh +291 -0
- package/scripts/agent/task-status.sh +70 -0
- package/scripts/agent/task-up.sh +3761 -0
- package/scripts/agent/uai-env-exec.sh +26 -0
- package/scripts/install/darwin.ts +1048 -0
- package/scripts/install/linux.ts +958 -0
- package/scripts/install/types.ts +78 -0
- package/scripts/install/util.ts +254 -0
- package/scripts/install/win.ts +143 -0
- package/src/activation-respawn.ts +155 -0
- package/src/apple-tunnel-route.ts +104 -0
- package/src/capture-wire.ts +102 -0
- package/src/cli.ts +2583 -0
- package/src/cloud-credentials.ts +2144 -0
- package/src/cloud-origins.ts +23 -0
- package/src/credential-location.ts +248 -0
- package/src/credential-wire-validation.ts +25 -0
- package/src/custom-connections.ts +237 -0
- package/src/engine-account-paths.ts +11 -0
- package/src/engine-models.ts +39 -0
- package/src/engine-usage.ts +104 -0
- package/src/event-outbox.ts +185 -0
- package/src/index.ts +2899 -0
- package/src/load-env.ts +97 -0
- package/src/machine-tunnel-route.ts +156 -0
- package/src/main.ts +6349 -0
- package/src/never-proxied-domains.ts +31 -0
- package/src/outbound-frame.ts +31 -0
- package/src/paths.ts +81 -0
- package/src/project-env-latest.ts +130 -0
- package/src/project-env-policy.ts +396 -0
- package/src/project-env-wording.ts +53 -0
- package/src/protocol.ts +5545 -0
- package/src/runtime-bootstrap.ts +165 -0
- package/src/station-identity.ts +96 -0
- package/src/task-env-names.ts +28 -0
- package/src/task-secrets.ts +75 -0
- package/src/tunnel-dial.ts +102 -0
- package/src/ui/server.ts +852 -0
- package/src/ui/types.ts +173 -0
- package/src/workspace-path-policy.ts +41 -0
- package/ui/app.js +1102 -0
- package/ui/index.html +90 -0
- package/ui/style.css +685 -0
- package/ui/uai-favicon.svg +21 -0
- package/ui/uai-logo-black.svg +9 -0
- package/ui/uai-wheel.svg +9 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import type { TaskEnvironmentHandle } from "./task-environment/types";
|
|
2
|
+
import { isEnvironmentTransportProvider } from "./task-environment/types";
|
|
3
|
+
import { MACHINE_GATEWAY_TUNNEL_PORT } from "./machine-gateway-tunnel";
|
|
4
|
+
import {
|
|
5
|
+
installMachineProjectEnvLoader,
|
|
6
|
+
installMachineTaskForwarderAsset,
|
|
7
|
+
} from "./machine-project-env-loader";
|
|
8
|
+
import { MCP_GATEWAY_PORT, taskGatewayHost } from "./mcp-gateway";
|
|
9
|
+
import { STANDARD_DEFAULT_NODE_VERSION } from "./standard-image";
|
|
10
|
+
import {
|
|
11
|
+
taskGatewayForwarderSecretInput,
|
|
12
|
+
taskGatewayRegistrationForRecovery,
|
|
13
|
+
} from "./task-gateway-state";
|
|
14
|
+
import { TASK_FORWARDER_SECRET_BYTES } from "./task-gateway-protocol";
|
|
15
|
+
import type { TaskGatewayRegistration } from "./task-gateway-state";
|
|
16
|
+
|
|
17
|
+
const STEP_OUTPUT_BYTES = 256 * 1024;
|
|
18
|
+
const APPLE_FORWARDER_NODE_PATH =
|
|
19
|
+
`/run/uai/asdf-lower/installs/nodejs/${STANDARD_DEFAULT_NODE_VERSION}/bin/node` as const;
|
|
20
|
+
|
|
21
|
+
type TaskForwarderNodePath =
|
|
22
|
+
| "/usr/bin/node"
|
|
23
|
+
| "/usr/local/bin/node"
|
|
24
|
+
| typeof APPLE_FORWARDER_NODE_PATH;
|
|
25
|
+
|
|
26
|
+
// Shared by initial machine provisioning and every recovery topology. Keep
|
|
27
|
+
// the ownership/process proof in lockstep with task-up.sh's container
|
|
28
|
+
// launcher. The generation secret arrives on stdin; no task file, argv entry,
|
|
29
|
+
// or environment variable ever contains it.
|
|
30
|
+
const START_TASK_FORWARDER = String.raw`set -eu
|
|
31
|
+
task_id=$1
|
|
32
|
+
generation=$2
|
|
33
|
+
gateway_host=$3
|
|
34
|
+
gateway_port=$4
|
|
35
|
+
node_path=$5
|
|
36
|
+
case "$node_path" in
|
|
37
|
+
/usr/bin/node | /usr/local/bin/node) ;;
|
|
38
|
+
${APPLE_FORWARDER_NODE_PATH})
|
|
39
|
+
[ ! -L "$node_path" ] && [ -f "$node_path" ] && [ -x "$node_path" ] \
|
|
40
|
+
|| exit 1
|
|
41
|
+
;;
|
|
42
|
+
*) exit 1 ;;
|
|
43
|
+
esac
|
|
44
|
+
case "$gateway_port" in ''|*[!0-9]*) exit 1 ;; esac
|
|
45
|
+
[ "$gateway_port" -ge 1 ] && [ "$gateway_port" -le 65535 ] || exit 1
|
|
46
|
+
state_dir=/run/uai/task-forwarder
|
|
47
|
+
pid_file=$state_dir/pid
|
|
48
|
+
ready_file=$state_dir/ready
|
|
49
|
+
error_file=$state_dir/error.log
|
|
50
|
+
umask 077
|
|
51
|
+
[ ! -L "$state_dir" ] || exit 1
|
|
52
|
+
[ ! -e "$state_dir" ] || [ -d "$state_dir" ] || exit 1
|
|
53
|
+
/usr/bin/install -d -o root -g root -m 0700 "$state_dir"
|
|
54
|
+
|
|
55
|
+
if [ -f "$pid_file" ] && [ ! -L "$pid_file" ]; then
|
|
56
|
+
old_pid=$(/bin/cat "$pid_file" 2>/dev/null || true)
|
|
57
|
+
case "$old_pid" in ''|*[!0-9]*) old_pid= ;; esac
|
|
58
|
+
if [ -n "$old_pid" ] && /bin/kill -0 "$old_pid" 2>/dev/null; then
|
|
59
|
+
old_uid=$(/usr/bin/awk '/^Uid:/ { print $2; exit }' "/proc/$old_pid/status" 2>/dev/null || true)
|
|
60
|
+
old_args=$(/usr/bin/tr '\000' '\n' < "/proc/$old_pid/cmdline" 2>/dev/null || true)
|
|
61
|
+
# /run survives a Docker stop/start. Its recorded pid may have been
|
|
62
|
+
# reused by uai-init or another innocent process; treat that record as
|
|
63
|
+
# stale and never signal a process unless both uid and argv prove it is
|
|
64
|
+
# this task's prior forwarder.
|
|
65
|
+
if [ "$old_uid" = 65532 ] \
|
|
66
|
+
&& printf '%s\n' "$old_args" | /bin/grep -Fxq '/usr/local/share/uai/uai-task-forwarder.mjs' \
|
|
67
|
+
&& printf '%s\n' "$old_args" | /bin/grep -Fxq "$task_id"; then
|
|
68
|
+
/bin/kill -TERM "$old_pid"
|
|
69
|
+
attempts=0
|
|
70
|
+
while /bin/kill -0 "$old_pid" 2>/dev/null && [ "$attempts" -lt 50 ]; do
|
|
71
|
+
/bin/sleep 0.1
|
|
72
|
+
attempts=$((attempts + 1))
|
|
73
|
+
done
|
|
74
|
+
if /bin/kill -0 "$old_pid" 2>/dev/null; then
|
|
75
|
+
/bin/kill -KILL "$old_pid"
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
fi
|
|
79
|
+
fi
|
|
80
|
+
/bin/rm -f -- "$pid_file" "$ready_file" "$error_file"
|
|
81
|
+
|
|
82
|
+
exec 3<&0
|
|
83
|
+
/usr/bin/env -i PATH=/usr/bin:/bin \
|
|
84
|
+
/usr/bin/setsid \
|
|
85
|
+
/usr/bin/setpriv \
|
|
86
|
+
--reuid=65532 \
|
|
87
|
+
--regid=65532 \
|
|
88
|
+
--clear-groups \
|
|
89
|
+
--bounding-set=-all \
|
|
90
|
+
--inh-caps=-all \
|
|
91
|
+
--ambient-caps=-all \
|
|
92
|
+
--no-new-privs \
|
|
93
|
+
"$node_path" \
|
|
94
|
+
/usr/local/share/uai/uai-task-forwarder.mjs \
|
|
95
|
+
--task-id "$task_id" \
|
|
96
|
+
--generation "$generation" \
|
|
97
|
+
--gateway-host "$gateway_host" \
|
|
98
|
+
--gateway-port "$gateway_port" \
|
|
99
|
+
--listen-port 5877 \
|
|
100
|
+
<&3 3<&- >"$ready_file" 2>"$error_file" &
|
|
101
|
+
forwarder_pid=$!
|
|
102
|
+
exec 3<&-
|
|
103
|
+
printf '%s\n' "$forwarder_pid" > "$pid_file"
|
|
104
|
+
|
|
105
|
+
attempts=0
|
|
106
|
+
while [ "$attempts" -lt 50 ]; do
|
|
107
|
+
/bin/kill -0 "$forwarder_pid" 2>/dev/null || exit 1
|
|
108
|
+
if [ -f "$ready_file" ] \
|
|
109
|
+
&& [ "$(/bin/cat "$ready_file" 2>/dev/null || true)" = READY ]; then
|
|
110
|
+
exit 0
|
|
111
|
+
fi
|
|
112
|
+
/bin/sleep 0.1
|
|
113
|
+
attempts=$((attempts + 1))
|
|
114
|
+
done
|
|
115
|
+
exit 1`;
|
|
116
|
+
|
|
117
|
+
export interface TaskEnvForwarderLaunchOptions {
|
|
118
|
+
gatewayHost: string;
|
|
119
|
+
gatewayPort: number;
|
|
120
|
+
nodePath: TaskForwarderNodePath;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Launch or replace one authenticated task forwarder. Consumes and wipes
|
|
124
|
+
* the caller-owned secret copy on every outcome. */
|
|
125
|
+
export async function launchTaskEnvForwarder(
|
|
126
|
+
environment: Pick<TaskEnvironmentHandle<unknown>, "exec">,
|
|
127
|
+
identity: TaskGatewayRegistration,
|
|
128
|
+
secret: Uint8Array,
|
|
129
|
+
options: TaskEnvForwarderLaunchOptions,
|
|
130
|
+
): Promise<void> {
|
|
131
|
+
if (
|
|
132
|
+
secret.byteLength !== TASK_FORWARDER_SECRET_BYTES ||
|
|
133
|
+
!Number.isSafeInteger(options.gatewayPort) ||
|
|
134
|
+
options.gatewayPort < 1 ||
|
|
135
|
+
options.gatewayPort > 65_535
|
|
136
|
+
) {
|
|
137
|
+
secret.fill(0);
|
|
138
|
+
throw new Error("task env forwarder launch parameters are invalid");
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const result = await environment.exec({
|
|
142
|
+
argv: [
|
|
143
|
+
"/bin/bash",
|
|
144
|
+
"-c",
|
|
145
|
+
START_TASK_FORWARDER,
|
|
146
|
+
"uai-task-forwarder",
|
|
147
|
+
identity.taskId,
|
|
148
|
+
identity.generation,
|
|
149
|
+
options.gatewayHost,
|
|
150
|
+
String(options.gatewayPort),
|
|
151
|
+
options.nodePath,
|
|
152
|
+
],
|
|
153
|
+
user: "root",
|
|
154
|
+
projectEnvProfile: "maintenance",
|
|
155
|
+
stdin: secret,
|
|
156
|
+
timeoutMs: 30_000,
|
|
157
|
+
maxOutputBytes: STEP_OUTPUT_BYTES,
|
|
158
|
+
});
|
|
159
|
+
if (
|
|
160
|
+
result.exitCode !== 0 ||
|
|
161
|
+
result.signal !== null ||
|
|
162
|
+
result.stdoutTruncated ||
|
|
163
|
+
result.stderrTruncated
|
|
164
|
+
) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`task env forwarder failed to start: ${Buffer.from(result.stderr)
|
|
167
|
+
.toString("utf8")
|
|
168
|
+
.trim()
|
|
169
|
+
.slice(-512) || `exit ${String(result.exitCode)}`}`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
} finally {
|
|
173
|
+
secret.fill(0);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Reassert the loader process after runtime recovery, before consumers.
|
|
178
|
+
* Uses the task only through `exec`, so a caller can fence every exec. */
|
|
179
|
+
export async function ensureContainerTaskEnvForwarder(
|
|
180
|
+
environment: Pick<TaskEnvironmentHandle<unknown>, "descriptor" | "exec">,
|
|
181
|
+
): Promise<void> {
|
|
182
|
+
const { descriptor } = environment;
|
|
183
|
+
if (descriptor.envMode !== "loader") return;
|
|
184
|
+
const isTransport = isEnvironmentTransportProvider(
|
|
185
|
+
descriptor.locator.provider,
|
|
186
|
+
);
|
|
187
|
+
const directMachine = descriptor.locator.provider === "machine";
|
|
188
|
+
if (isTransport && !directMachine) {
|
|
189
|
+
// A fixed-task sidecar serves its own loopback and must not bind a second
|
|
190
|
+
// listener. Other non-machine transports share that conservative shape.
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (directMachine) {
|
|
194
|
+
// A machine reboot preserves disk but not /run or the uid-65532 process.
|
|
195
|
+
// Refresh the current runner's root-owned assets before exporting the
|
|
196
|
+
// generation secret, then recreate the process below.
|
|
197
|
+
await installMachineProjectEnvLoader(environment);
|
|
198
|
+
await installMachineTaskForwarderAsset(environment);
|
|
199
|
+
}
|
|
200
|
+
const identity = taskGatewayRegistrationForRecovery(descriptor.taskId);
|
|
201
|
+
if (identity === null) {
|
|
202
|
+
throw new Error("task env gateway identity is not active during recovery");
|
|
203
|
+
}
|
|
204
|
+
const launch: TaskEnvForwarderLaunchOptions = directMachine
|
|
205
|
+
? {
|
|
206
|
+
gatewayHost: "127.0.0.1",
|
|
207
|
+
gatewayPort: MACHINE_GATEWAY_TUNNEL_PORT,
|
|
208
|
+
nodePath: "/usr/bin/node",
|
|
209
|
+
}
|
|
210
|
+
: {
|
|
211
|
+
gatewayHost: await taskGatewayHost(),
|
|
212
|
+
gatewayPort: MCP_GATEWAY_PORT,
|
|
213
|
+
nodePath:
|
|
214
|
+
descriptor.locator.provider === "apple-container"
|
|
215
|
+
? APPLE_FORWARDER_NODE_PATH
|
|
216
|
+
: "/usr/local/bin/node",
|
|
217
|
+
};
|
|
218
|
+
// The secret copy is taken last, with nothing left to await before the
|
|
219
|
+
// launch, which wipes it on every outcome: no failure strands it.
|
|
220
|
+
const secret = taskGatewayForwarderSecretInput(
|
|
221
|
+
identity.taskId,
|
|
222
|
+
identity.generation,
|
|
223
|
+
);
|
|
224
|
+
if (secret === null) {
|
|
225
|
+
throw new Error("task env gateway secret is unavailable during recovery");
|
|
226
|
+
}
|
|
227
|
+
await launchTaskEnvForwarder(environment, identity, secret, launch);
|
|
228
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { deliveredTaskSecrets } from "./credential-consumers/task-secrets";
|
|
2
|
+
/**
|
|
3
|
+
* ADR-153 §4 and §6: where a task's project env comes from, for its /env and
|
|
4
|
+
* its proxy alike, on an owned runner and on a task machine's sidecar.
|
|
5
|
+
*
|
|
6
|
+
* - The store is the runner's own records on an owned runner, and on a task
|
|
7
|
+
* machine the sidecar's replica of the cloud's.
|
|
8
|
+
* - A store never activated gives tasks the stored values, as before
|
|
9
|
+
* records existed.
|
|
10
|
+
* - Once activated, a task's env is the view of one snapshot of the store,
|
|
11
|
+
* decided with the task's own schema observations: each key's value as of
|
|
12
|
+
* its head, a placeholder the proxy swaps, or nothing. While the store's
|
|
13
|
+
* gate is closed, or a read of it fails, the task gets no project env at
|
|
14
|
+
* all, never the stored values.
|
|
15
|
+
*
|
|
16
|
+
* A task's read of the store is kept until anything it depends on changes:
|
|
17
|
+
* one of its projects' values or records, a binding or the store's gate
|
|
18
|
+
* (each bumps a process-local revision, and only this process writes the
|
|
19
|
+
* store). Its observations are stored once and never change. So a change
|
|
20
|
+
* reaches the next command and the next request, while a command that
|
|
21
|
+
* changes nothing reads nothing.
|
|
22
|
+
*
|
|
23
|
+
* ADR-154: once the runner begins leaving its own credentials, neither
|
|
24
|
+
* store is read. On cloud credentials a task's env is what the composer
|
|
25
|
+
* serves for exactly the run the reading generation serves (/env's gateway
|
|
26
|
+
* generation, the proxy's live one) while that run is the task's open one,
|
|
27
|
+
* read afresh every time (its gate closes the moment the run's credentials
|
|
28
|
+
* do, and no copy of it is kept here). Until the runner is active, a task
|
|
29
|
+
* gets no project env at all.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { activeCloudCredentials, onLeavingLegacy, onOwnCredentials } from "./cloud-credential-host";
|
|
33
|
+
import { noteProjectEnvViewChange, projectEnvAuthorityRevision, projectEnvRevision } from "./host-env";
|
|
34
|
+
import { readProjectEnvAuthoritySnapshot } from "./project-env-authority";
|
|
35
|
+
import { readProjectEnvReplicaSnapshot, readProjectEnvReplicaStatus } from "./project-env-replica";
|
|
36
|
+
import { taskProjectEnvResolver, type TaskEnvSource } from "./project-env-loader";
|
|
37
|
+
import { captureTaskEnvSchemas, taskEnvSchemaObservation } from "./project-env-schema-capture";
|
|
38
|
+
import {
|
|
39
|
+
sameTaskEnvHead,
|
|
40
|
+
taskEnvView,
|
|
41
|
+
taskEnvWithheldNote,
|
|
42
|
+
type TaskEnvHead,
|
|
43
|
+
type TaskEnvSnapshot,
|
|
44
|
+
} from "./project-env-task-policy";
|
|
45
|
+
import { getHostTask, hostTaskProjectEnvSpec, readTaskRunEpochState, type TaskProjectEnvSpec } from "./runtime-state";
|
|
46
|
+
import { sidecarMode } from "./sidecar";
|
|
47
|
+
import { taskProxiedEnv } from "./secrets-proxy/policy";
|
|
48
|
+
import { taskTrustEnv } from "./secrets-proxy/readiness";
|
|
49
|
+
import { currentTaskGatewayRun, type TaskGatewayEnvResolver } from "./task-gateway-state";
|
|
50
|
+
|
|
51
|
+
/** The task's projects, lead first, as its launch pinned them. */
|
|
52
|
+
function projectIdsOf(spec: TaskProjectEnvSpec): string[] {
|
|
53
|
+
const ordered = [...spec.projects].sort(
|
|
54
|
+
(left, right) => left.position - right.position || (left.projectId < right.projectId ? -1 : left.projectId > right.projectId ? 1 : 0),
|
|
55
|
+
);
|
|
56
|
+
return [...new Set(ordered.map((project) => project.projectId))];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* One snapshot of the task's projects: `legacy` on a store that has never
|
|
61
|
+
* been activated, null while its gate is closed or it cannot be read, and
|
|
62
|
+
* otherwise the records, values and observations the task's decisions come
|
|
63
|
+
* from.
|
|
64
|
+
*/
|
|
65
|
+
export function readTaskEnvSnapshot(
|
|
66
|
+
taskId: string,
|
|
67
|
+
spec: TaskProjectEnvSpec,
|
|
68
|
+
runEpoch: number | null,
|
|
69
|
+
): TaskEnvSnapshot | "legacy" | null {
|
|
70
|
+
const projectIds = projectIdsOf(spec);
|
|
71
|
+
if (!onOwnCredentials()) return cloudTaskEnvSnapshot(taskId, projectIds, runEpoch);
|
|
72
|
+
const key = JSON.stringify([projectEnvAuthorityRevision(), projectIds.map((projectId) => [projectId, projectEnvRevision(projectId)])]);
|
|
73
|
+
const hit = snapshots.get(taskId);
|
|
74
|
+
if (hit?.key === key) return hit.snapshot;
|
|
75
|
+
const snapshot = freshTaskEnvSnapshot(taskId, projectIds);
|
|
76
|
+
// Kept only when nothing in it can change without a revision moving: a
|
|
77
|
+
// read that failed, or one with an observation not stored yet (task-up
|
|
78
|
+
// may still be about to record its base), is read again next time.
|
|
79
|
+
if (snapshot === "legacy" || (snapshot !== null && projectIds.every((projectId) => taskEnvSchemaObservation(taskId, projectId) !== null))) {
|
|
80
|
+
snapshots.delete(taskId);
|
|
81
|
+
if (snapshots.size >= MAX_SNAPSHOTS) snapshots.delete(snapshots.keys().next().value!);
|
|
82
|
+
snapshots.set(taskId, { key, snapshot });
|
|
83
|
+
}
|
|
84
|
+
return snapshot;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Tasks' last reads, most recent last. Values stay in memory only here and
|
|
88
|
+
* in the rendered /env documents, as before. */
|
|
89
|
+
const snapshots = new Map<string, { key: string; snapshot: TaskEnvSnapshot | "legacy" }>();
|
|
90
|
+
const MAX_SNAPSHOTS = 256;
|
|
91
|
+
|
|
92
|
+
/** Forget a task's read, when its gateway generation goes. */
|
|
93
|
+
export function forgetTaskEnvSnapshot(taskId: string): void {
|
|
94
|
+
snapshots.delete(taskId);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* ADR-154: the task's env as the composer serves it for `runEpoch`, the
|
|
99
|
+
* reading generation's run, with the task's own observations; null unless
|
|
100
|
+
* the runner is active and that run is the task's admitted, open one (a
|
|
101
|
+
* generation from an older runtime gets nothing while a newer run starts),
|
|
102
|
+
* or while the composer serves nothing for it.
|
|
103
|
+
*/
|
|
104
|
+
function cloudTaskEnvSnapshot(
|
|
105
|
+
taskId: string,
|
|
106
|
+
projectIds: readonly string[],
|
|
107
|
+
runEpoch: number | null,
|
|
108
|
+
): TaskEnvSnapshot | null {
|
|
109
|
+
const composer = activeCloudCredentials();
|
|
110
|
+
if (composer === null || runEpoch === null) return null;
|
|
111
|
+
const run = readTaskRunEpochState(taskId);
|
|
112
|
+
if (run === null || !run.assignmentOpen || run.highestRunEpoch !== runEpoch) return null;
|
|
113
|
+
const served = composer.envSnapshot({ taskId, runEpoch }, projectIds);
|
|
114
|
+
if (served === null) return null;
|
|
115
|
+
return snapshotOf(taskId, projectIds, (projectId) =>
|
|
116
|
+
(served.projects.find((project) => project.projectId === projectId)?.entries ?? []).map((entry) => ({
|
|
117
|
+
key: entry.state.key,
|
|
118
|
+
state: entry.state,
|
|
119
|
+
value: entry.materialization.kind === "installed" ? entry.value : null,
|
|
120
|
+
})),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function freshTaskEnvSnapshot(taskId: string, projectIds: readonly string[]): TaskEnvSnapshot | "legacy" | null {
|
|
125
|
+
return sidecarMode() ? replicaTaskEnvSnapshot(taskId, projectIds) : ownedTaskEnvSnapshot(taskId, projectIds);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** An owned runner is the authority for its own records. */
|
|
129
|
+
function ownedTaskEnvSnapshot(taskId: string, projectIds: readonly string[]): TaskEnvSnapshot | "legacy" | null {
|
|
130
|
+
const read = readProjectEnvAuthoritySnapshot(projectIds);
|
|
131
|
+
if (read === null) return null;
|
|
132
|
+
if (read.mode === "legacy") return "legacy";
|
|
133
|
+
return snapshotOf(taskId, projectIds, (projectId) => read.projects.find((project) => project.projectId === projectId)?.entries ?? []);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* On a task machine the cloud is the authority, and the sidecar keeps a
|
|
138
|
+
* replica of its task's records (ADR-153 §4, "A machine's replica"): once it
|
|
139
|
+
* has been activated, the task's view comes from it, and a key whose value
|
|
140
|
+
* the sidecar has not installed gets none. Before that, the values the
|
|
141
|
+
* sidecar reads from the store by name, as before.
|
|
142
|
+
*/
|
|
143
|
+
function replicaTaskEnvSnapshot(taskId: string, projectIds: readonly string[]): TaskEnvSnapshot | "legacy" | null {
|
|
144
|
+
let activated: boolean;
|
|
145
|
+
try {
|
|
146
|
+
activated = readProjectEnvReplicaStatus().activated;
|
|
147
|
+
} catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
if (!activated) return "legacy";
|
|
151
|
+
const read = readProjectEnvReplicaSnapshot(projectIds);
|
|
152
|
+
if (read === null) return null;
|
|
153
|
+
return snapshotOf(taskId, projectIds, (projectId) =>
|
|
154
|
+
(read.projects.find((project) => project.projectId === projectId)?.entries ?? []).map((entry) => ({
|
|
155
|
+
key: entry.state.key,
|
|
156
|
+
state: entry.state,
|
|
157
|
+
value: entry.materialization.kind === "installed" ? entry.value : null,
|
|
158
|
+
})),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
interface StoreEntry {
|
|
163
|
+
key: string;
|
|
164
|
+
/** The record; null on a store that predates records. */
|
|
165
|
+
state: { head: TaskEnvHead; binding: unknown; policyEpoch: unknown } | null;
|
|
166
|
+
/** Null where the store holds no value for the record. */
|
|
167
|
+
value: string | null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** The task's snapshot of one read of the store, with its observations. */
|
|
171
|
+
function snapshotOf(taskId: string, projectIds: readonly string[], entriesOf: (projectId: string) => readonly StoreEntry[]): TaskEnvSnapshot {
|
|
172
|
+
const projects = projectIds.map((projectId) => {
|
|
173
|
+
const entries = entriesOf(projectId);
|
|
174
|
+
return {
|
|
175
|
+
projectId,
|
|
176
|
+
// Captured here on the task's first read, from the base task-up
|
|
177
|
+
// recorded before the container started; with none recorded yet, a
|
|
178
|
+
// schema it could not read, so an unbound key is withheld.
|
|
179
|
+
observation: taskEnvSchemaObservation(taskId, projectId) ?? captureTaskEnvSchemas(taskId, [projectId], { final: false })[0],
|
|
180
|
+
records: entries.flatMap((entry) =>
|
|
181
|
+
entry.state === null
|
|
182
|
+
? []
|
|
183
|
+
: [{ key: entry.key, head: entry.state.head, binding: entry.state.binding, policyEpoch: entry.state.policyEpoch }],
|
|
184
|
+
),
|
|
185
|
+
entries,
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
return {
|
|
189
|
+
projects: projects.map(({ projectId, observation, records }) => ({ projectId, observation, records })),
|
|
190
|
+
valueAt: (projectId: string, key: string, head: TaskEnvHead): string | null => {
|
|
191
|
+
const entry = projects.find((project) => project.projectId === projectId)?.entries.find((candidate) => candidate.key === key);
|
|
192
|
+
return entry?.state != null && sameTaskEnvHead(entry.state.head, head) ? entry.value : null;
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Where the task's /env takes its project values from right now, for a
|
|
198
|
+
* generation serving `runEpoch`. */
|
|
199
|
+
export function taskEnvSource(taskId: string, spec: TaskProjectEnvSpec, runEpoch: number | null): TaskEnvSource {
|
|
200
|
+
const snapshot = readTaskEnvSnapshot(taskId, spec, runEpoch);
|
|
201
|
+
if (snapshot === "legacy") return { kind: "stored" };
|
|
202
|
+
if (snapshot === null) return { kind: "none" };
|
|
203
|
+
return { kind: "view", view: taskEnvView(snapshot) };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The task's snapshot for its proxy, by task id alone: what its persisted
|
|
207
|
+
* launch spec names, for the run its live gateway generation serves.
|
|
208
|
+
* Nothing is proxied from a store never activated. */
|
|
209
|
+
export function proxyTaskEnvSnapshot(taskId: string): TaskEnvSnapshot | null {
|
|
210
|
+
const task = getHostTask(taskId);
|
|
211
|
+
let spec: TaskProjectEnvSpec | null = null;
|
|
212
|
+
try {
|
|
213
|
+
spec = task === null ? null : hostTaskProjectEnvSpec(task);
|
|
214
|
+
} catch {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
if (spec === null) return null;
|
|
218
|
+
const snapshot = readTaskEnvSnapshot(taskId, spec, currentTaskGatewayRun(taskId));
|
|
219
|
+
return snapshot === "legacy" ? null : snapshot;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** The task's note about the keys it does not get, from the store as it is
|
|
223
|
+
* now; null when it gets every one, or the store gives no view. */
|
|
224
|
+
export function taskEnvWithheldNoteNow(taskId: string, spec: TaskProjectEnvSpec, runEpoch: number | null): string | null {
|
|
225
|
+
const source = taskEnvSource(taskId, spec, runEpoch);
|
|
226
|
+
return source.kind === "view" ? taskEnvWithheldNote(source.view.policies) : null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** The resolver a loader task's gateway generation renders /env with, for
|
|
230
|
+
* the run that generation serves (null: one from before run epochs). */
|
|
231
|
+
export function taskEnvResolver(taskId: string, spec: TaskProjectEnvSpec, runEpoch: number | null): TaskGatewayEnvResolver {
|
|
232
|
+
return taskProjectEnvResolver(
|
|
233
|
+
spec,
|
|
234
|
+
() => taskTrustEnv(taskId),
|
|
235
|
+
() => taskProxiedEnv(taskId),
|
|
236
|
+
() => taskEnvSource(taskId, spec, runEpoch),
|
|
237
|
+
() => deliveredTaskSecrets(taskId, runEpoch),
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* ADR-154: the moment the runner begins leaving its own credentials, every
|
|
243
|
+
* read above stops taking their values (each checks `onOwnCredentials()`),
|
|
244
|
+
* and whoever follows the env (the proxy's trust delivery, which unpins
|
|
245
|
+
* what is no longer proxied) looks again. Registered once, at boot.
|
|
246
|
+
*/
|
|
247
|
+
export function followLeavingLegacy(): () => void {
|
|
248
|
+
return onLeavingLegacy(() => noteProjectEnvViewChange());
|
|
249
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-154 §6, for providers whose runtime is a container or a machine they
|
|
3
|
+
* exec into (Docker, Apple, a machine over ssh): an anchored request runs
|
|
4
|
+
* as root, the engine supervisor first, which drops to the task user the
|
|
5
|
+
* request would have run as before it starts the request's own argv.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Who a request with no user runs as in a task runtime, as the engines
|
|
9
|
+
* always have (their homes are under /home/node). */
|
|
10
|
+
export const ANCHORED_TASK_USER = "node";
|
|
11
|
+
|
|
12
|
+
/** `request`, run as root under its anchor; unchanged without one. */
|
|
13
|
+
export function rootAnchoredRequest<
|
|
14
|
+
T extends {
|
|
15
|
+
argv: readonly [string, ...string[]];
|
|
16
|
+
user?: string;
|
|
17
|
+
anchor?: { readonly prefix: readonly [string, ...string[]] };
|
|
18
|
+
},
|
|
19
|
+
>(request: T): T {
|
|
20
|
+
const { anchor } = request;
|
|
21
|
+
if (!anchor) return request;
|
|
22
|
+
if (request.user === "root") {
|
|
23
|
+
throw new Error("an anchored process runs as a task user, never as root");
|
|
24
|
+
}
|
|
25
|
+
const { anchor: _anchor, ...rest } = request;
|
|
26
|
+
return {
|
|
27
|
+
...rest,
|
|
28
|
+
user: "root",
|
|
29
|
+
argv: [...anchor.prefix, request.user ?? ANCHORED_TASK_USER, "--", ...request.argv],
|
|
30
|
+
} as unknown as T;
|
|
31
|
+
}
|