@moondeck/station 0.0.1 → 1.0.1
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/0031_first_turn_receipts.sql +9 -0
- package/db/migrations/meta/_journal.json +230 -0
- package/db/schema.ts +871 -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 +1470 -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 +937 -0
- package/lib/env.ts +195 -0
- package/lib/first-turn-receipts.ts +62 -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 +115 -8
- 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/bridge-diagnostics.ts +39 -0
- package/src/capture-wire.ts +102 -0
- package/src/cli.ts +2585 -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/enrollment-next-steps.ts +24 -0
- package/src/event-outbox.ts +185 -0
- package/src/index.ts +2907 -0
- package/src/load-env.ts +97 -0
- package/src/machine-tunnel-route.ts +156 -0
- package/src/main.ts +6363 -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 +5550 -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
- package/bin/moondeck-station.mjs +0 -3
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1.7
|
|
2
|
+
#
|
|
3
|
+
# uai standard image — the ONE per-host workbench image (ADR-022).
|
|
4
|
+
#
|
|
5
|
+
# Built once per host at host startup and cached as `uai-standard:dev`.
|
|
6
|
+
# A task reuses this image unchanged unless a selected project declares an
|
|
7
|
+
# `extra` snippet, in which case task-up derives `uai-task-<id>` FROM this
|
|
8
|
+
# image. See ../../../docs/runtime.md.
|
|
9
|
+
#
|
|
10
|
+
# Design points:
|
|
11
|
+
# - Debian bookworm-slim base, non-root `node` user (home /home/node).
|
|
12
|
+
# - asdf at /opt/asdf with plugins for nodejs/python/golang/ruby/rust.
|
|
13
|
+
# Before Compose starts a task, a host-controlled maintenance container
|
|
14
|
+
# materializes its declared versions into the host-wide /opt/asdf-data
|
|
15
|
+
# volume. uai-init only installs task-local dependencies afterward.
|
|
16
|
+
# - A default ~/.tool-versions (asdf global fallback) pins a working Node +
|
|
17
|
+
# Python so the agent CLIs and a 0-project scratchpad always have a runtime;
|
|
18
|
+
# a project's own .tool-versions takes precedence (asdf dir walk).
|
|
19
|
+
# - Claude Code + Codex + code-server installed globally via the root Node.
|
|
20
|
+
#
|
|
21
|
+
# This Dockerfile takes NO build args and substitutes NO placeholders — it is
|
|
22
|
+
# fully static. Per-task variation lives in the derived `uai-task-<id>` image.
|
|
23
|
+
|
|
24
|
+
FROM debian:bookworm-slim
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# 0. Pinned versions (single source of truth for this image).
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
# asdf release to clone. Pinned for reproducible builds; bump deliberately.
|
|
31
|
+
ENV ASDF_VERSION=v0.14.1
|
|
32
|
+
|
|
33
|
+
# Qualified agent builds. These are execution-contract pins: per-command env
|
|
34
|
+
# freshness is requalified before any of them moves.
|
|
35
|
+
ENV UAI_CODEX_CLI_VERSION=0.164.0
|
|
36
|
+
ENV UAI_CURSOR_AGENT_VERSION=2026.10.22-27dcf5f
|
|
37
|
+
ENV UAI_GROK_CLI_VERSION=1.0.70
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# 1. System packages.
|
|
41
|
+
#
|
|
42
|
+
# build-essential + the -dev libs cover what asdf's nodejs/python/ruby/rust
|
|
43
|
+
# build plugins need to compile a runtime from source on first use. unzip is
|
|
44
|
+
# needed by several asdf plugins; gnupg + ca-certificates for the gh apt repo.
|
|
45
|
+
# iptables is for the secrets proxy's NAT chain (ADR-153); only the runner's
|
|
46
|
+
# one privileged exec runs it, and ca-certificates also installs its CA.
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
50
|
+
|
|
51
|
+
RUN apt-get update \
|
|
52
|
+
&& apt-get install -y --no-install-recommends \
|
|
53
|
+
build-essential \
|
|
54
|
+
ca-certificates \
|
|
55
|
+
curl \
|
|
56
|
+
direnv \
|
|
57
|
+
git \
|
|
58
|
+
gnupg \
|
|
59
|
+
iptables \
|
|
60
|
+
jq \
|
|
61
|
+
libbz2-dev \
|
|
62
|
+
libffi-dev \
|
|
63
|
+
liblzma-dev \
|
|
64
|
+
libncurses-dev \
|
|
65
|
+
libreadline-dev \
|
|
66
|
+
libsqlite3-dev \
|
|
67
|
+
libssl-dev \
|
|
68
|
+
libxml2-dev \
|
|
69
|
+
libxmlsec1-dev \
|
|
70
|
+
libyaml-dev \
|
|
71
|
+
openssh-client \
|
|
72
|
+
pkg-config \
|
|
73
|
+
procps \
|
|
74
|
+
ripgrep \
|
|
75
|
+
tk-dev \
|
|
76
|
+
tmux \
|
|
77
|
+
unzip \
|
|
78
|
+
util-linux \
|
|
79
|
+
xz-utils \
|
|
80
|
+
zlib1g-dev \
|
|
81
|
+
zsh \
|
|
82
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
83
|
+
|
|
84
|
+
# ADR-053: agent browser (Playwright/Chromium) runtime libs + the watchable
|
|
85
|
+
# display stack (Xvfb → x11vnc → noVNC). Baked here because the
|
|
86
|
+
# per-container background apt raced session start (headless-stuck MCP
|
|
87
|
+
# server, viewer lagging the preview by minutes — Debian's novnc drags a
|
|
88
|
+
# large Python tree). The Chromium BINARY still comes from the shared
|
|
89
|
+
# uai-playwright volume (downloaded once per host); these are its deps.
|
|
90
|
+
RUN apt-get update \
|
|
91
|
+
&& apt-get install -y --no-install-recommends \
|
|
92
|
+
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 \
|
|
93
|
+
libxkbcommon0 libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 \
|
|
94
|
+
libxrandr2 libgbm1 libasound2 libpango-1.0-0 libcairo2 \
|
|
95
|
+
fonts-liberation fonts-unifont \
|
|
96
|
+
xvfb x11vnc novnc websockify \
|
|
97
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
98
|
+
|
|
99
|
+
# GitHub CLI (`gh`) — used by the ship/PR flow inside the container.
|
|
100
|
+
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
|
101
|
+
| tee /usr/share/keyrings/githubcli-archive-keyring.gpg >/dev/null \
|
|
102
|
+
&& chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
|
|
103
|
+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|
|
104
|
+
> /etc/apt/sources.list.d/github-cli.list \
|
|
105
|
+
&& apt-get update \
|
|
106
|
+
&& apt-get install -y --no-install-recommends gh \
|
|
107
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
108
|
+
|
|
109
|
+
# uv — fast Python package + venv manager (used by uai-init's Python path).
|
|
110
|
+
# Installed system-wide so both root and node can run it.
|
|
111
|
+
RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/bin sh
|
|
112
|
+
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
# 2. Non-root `node` user.
|
|
115
|
+
#
|
|
116
|
+
# The container runs as `node`; uai-init and the agent CLIs expect
|
|
117
|
+
# /home/node. uid/gid 1000 matches the common host default so bind-mounted
|
|
118
|
+
# worktree files stay writable.
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
RUN groupadd --gid 1000 node \
|
|
122
|
+
&& useradd --uid 1000 --gid 1000 --create-home --home-dir /home/node --shell /bin/bash node
|
|
123
|
+
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
# 3. asdf.
|
|
126
|
+
#
|
|
127
|
+
# Clone to /opt/asdf (the code) and point the DATA dir at /opt/asdf-data
|
|
128
|
+
# (the host-wide cache volume mounts there). Both root and node must be able
|
|
129
|
+
# to read the install and write the data dir, so /opt/asdf-data is group
|
|
130
|
+
# owned by `node` and group-writable; the data volume inherits this.
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
ENV ASDF_DIR=/opt/asdf
|
|
134
|
+
ENV ASDF_DATA_DIR=/opt/asdf-data
|
|
135
|
+
ENV ASDF_CONFIG_FILE=/dev/null
|
|
136
|
+
ENV ASDF_DEFAULT_TOOL_VERSIONS_FILENAME=.tool-versions
|
|
137
|
+
ENV COREPACK_HOME=/opt/asdf-data/corepack
|
|
138
|
+
ENV COREPACK_DEFAULT_TO_LATEST=0
|
|
139
|
+
ENV COREPACK_ENABLE_AUTO_PIN=0
|
|
140
|
+
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
|
141
|
+
ENV COREPACK_ENV_FILE=0
|
|
142
|
+
ENV COREPACK_ENABLE_PROJECT_SPEC=1
|
|
143
|
+
ENV COREPACK_ENABLE_STRICT=1
|
|
144
|
+
ENV COREPACK_ENABLE_UNSAFE_CUSTOM_URLS=0
|
|
145
|
+
ENV COREPACK_INTEGRITY_CHECK=1
|
|
146
|
+
ENV COREPACK_ENABLE_NETWORK=0
|
|
147
|
+
ENV COREPACK_NPM_REGISTRY=https://registry.npmjs.org
|
|
148
|
+
# Leave the tool-versions filename at asdf's default (`.tool-versions`) so asdf
|
|
149
|
+
# walks UP from the cwd and a project's /workspace/<slug>/.tool-versions wins.
|
|
150
|
+
# The baked default lives at the node user's home (~/.tool-versions) as asdf's
|
|
151
|
+
# global fallback for tasks with no project file (agent CLIs, scratchpad).
|
|
152
|
+
# Keep ASDF_DEFAULT_TOOL_VERSIONS_FILENAME as the basename above, never an
|
|
153
|
+
# absolute path: an absolute value bypasses project files entirely (it made
|
|
154
|
+
# asdf read only /etc/.tool-versions, ignoring per-project pins).
|
|
155
|
+
|
|
156
|
+
RUN git clone --depth 1 --branch "$ASDF_VERSION" https://github.com/asdf-vm/asdf.git "$ASDF_DIR" \
|
|
157
|
+
&& mkdir -p "$ASDF_DATA_DIR" \
|
|
158
|
+
&& touch "$ASDF_DATA_DIR/.uai-maintenance.lock" \
|
|
159
|
+
&& chown -R node:node "$ASDF_DIR" "$ASDF_DATA_DIR" \
|
|
160
|
+
&& chmod -R g+rwX "$ASDF_DATA_DIR" \
|
|
161
|
+
&& chmod 0664 "$ASDF_DATA_DIR/.uai-maintenance.lock"
|
|
162
|
+
|
|
163
|
+
# Make asdf available on PATH + shell init for BOTH root and node, for login
|
|
164
|
+
# and non-login shells (uai-init runs as node via a non-interactive exec).
|
|
165
|
+
# - asdf.sh sources the shim + completions and prepends the shims dir.
|
|
166
|
+
# - The shims dir is also added to PATH directly so a bare `docker exec`
|
|
167
|
+
# (which sources neither profile) still sees installed runtimes.
|
|
168
|
+
ENV PATH=/opt/asdf-data/shims:/opt/asdf/bin:$PATH
|
|
169
|
+
|
|
170
|
+
RUN printf '%s\n' \
|
|
171
|
+
'. /opt/asdf/asdf.sh' \
|
|
172
|
+
> /etc/profile.d/asdf.sh \
|
|
173
|
+
&& chmod 0644 /etc/profile.d/asdf.sh
|
|
174
|
+
|
|
175
|
+
# Source asdf from each user's bashrc as well (interactive non-login shells,
|
|
176
|
+
# e.g. the code-server terminal) and enable direnv.
|
|
177
|
+
RUN for rc in /root/.bashrc /home/node/.bashrc; do \
|
|
178
|
+
printf '\n%s\n%s\n' \
|
|
179
|
+
'. /opt/asdf/asdf.sh' \
|
|
180
|
+
'eval "$(direnv hook bash)"' >> "$rc"; \
|
|
181
|
+
done \
|
|
182
|
+
&& chown node:node /home/node/.bashrc
|
|
183
|
+
|
|
184
|
+
# zsh + oh-my-zsh as the interactive login shell for `node` (the code-server
|
|
185
|
+
# terminal and `docker exec -it`). Installed unattended; the installer writes a
|
|
186
|
+
# default ~/.zshrc (robbyrussell theme, git plugin) which we extend with the
|
|
187
|
+
# same asdf + direnv wiring as .bashrc. Non-interactive execs (uai-init, claude,
|
|
188
|
+
# codex, git) invoke their command directly, so the login-shell change does not
|
|
189
|
+
# affect them.
|
|
190
|
+
RUN su node -c 'sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended' \
|
|
191
|
+
&& printf '\n%s\n%s\n' \
|
|
192
|
+
'. /opt/asdf/asdf.sh' \
|
|
193
|
+
'eval "$(direnv hook zsh)"' >> /home/node/.zshrc \
|
|
194
|
+
&& chown node:node /home/node/.zshrc \
|
|
195
|
+
&& usermod -s /usr/bin/zsh node
|
|
196
|
+
|
|
197
|
+
# ---------------------------------------------------------------------------
|
|
198
|
+
# 4. asdf plugins + the root default runtimes.
|
|
199
|
+
#
|
|
200
|
+
# Plugins are added for all common runtimes; only the root defaults are
|
|
201
|
+
# *installed* at build time. A pre-Compose maintenance container installs
|
|
202
|
+
# project versions into the shared /opt/asdf-data volume.
|
|
203
|
+
#
|
|
204
|
+
# Run as node so the install lands in /opt/asdf-data owned by node.
|
|
205
|
+
# ---------------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
# Default .tool-versions at the node user's home = asdf's GLOBAL fallback, used
|
|
208
|
+
# only when no project /workspace/<slug>/.tool-versions is in scope (agent CLIs,
|
|
209
|
+
# scratchpad tasks). A project file takes precedence via asdf's dir walk. The
|
|
210
|
+
# `tool-versions` file in this directory is the AUTHORITATIVE source.
|
|
211
|
+
COPY tool-versions /home/node/.tool-versions
|
|
212
|
+
COPY --chown=root:root container/corepack-version /usr/local/share/uai/corepack-version
|
|
213
|
+
COPY --chown=root:root container/uai-materialize-runtimes /usr/local/bin/uai-materialize-runtimes
|
|
214
|
+
RUN chmod 0644 /home/node/.tool-versions /usr/local/share/uai/corepack-version \
|
|
215
|
+
&& chmod 0755 /usr/local/bin/uai-materialize-runtimes \
|
|
216
|
+
&& chown node:node /home/node/.tool-versions
|
|
217
|
+
|
|
218
|
+
USER node
|
|
219
|
+
|
|
220
|
+
# Add plugins for all common runtimes, then install exactly the versions the
|
|
221
|
+
# default ~/.tool-versions pins (read straight from the file so there is one
|
|
222
|
+
# source of truth). Other versions install in the pre-Compose materializer.
|
|
223
|
+
RUN bash -lc '\
|
|
224
|
+
set -e; \
|
|
225
|
+
. /opt/asdf/asdf.sh; \
|
|
226
|
+
asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git; \
|
|
227
|
+
asdf plugin add python https://github.com/asdf-community/asdf-python.git; \
|
|
228
|
+
asdf plugin add golang https://github.com/asdf-community/asdf-golang.git; \
|
|
229
|
+
asdf plugin add ruby https://github.com/asdf-vm/asdf-ruby.git; \
|
|
230
|
+
asdf plugin add rust https://github.com/asdf-community/asdf-rust.git; \
|
|
231
|
+
cd /home/node && asdf install nodejs && asdf install python; \
|
|
232
|
+
'
|
|
233
|
+
|
|
234
|
+
# ---------------------------------------------------------------------------
|
|
235
|
+
# 5. Global agent CLIs + code-server, via the root-default Node.
|
|
236
|
+
#
|
|
237
|
+
# These are Node programs and must be present even for a 0-project task, so
|
|
238
|
+
# they are installed globally against the asdf-default Node and then reshimmed
|
|
239
|
+
# so `claude` / `codex` / `code-server` resolve through the asdf shims dir
|
|
240
|
+
# (which is on PATH for a bare docker exec).
|
|
241
|
+
# ---------------------------------------------------------------------------
|
|
242
|
+
|
|
243
|
+
RUN bash -lc '\
|
|
244
|
+
set -e; \
|
|
245
|
+
. /opt/asdf/asdf.sh; \
|
|
246
|
+
COREPACK_VERSION="$(sed -n "1p" /usr/local/share/uai/corepack-version)"; \
|
|
247
|
+
printf "%s" "$COREPACK_VERSION" | grep -Eq "^[0-9]+\\.[0-9]+\\.[0-9]+$"; \
|
|
248
|
+
npm install -g @anthropic-ai/claude-code "@openai/codex@$UAI_CODEX_CLI_VERSION" "corepack@$COREPACK_VERSION" \
|
|
249
|
+
--registry=https://registry.npmjs.org/ \
|
|
250
|
+
--userconfig=/dev/null \
|
|
251
|
+
--globalconfig=/dev/uai-empty-npmrc; \
|
|
252
|
+
corepack enable; \
|
|
253
|
+
curl -fsSL https://code-server.dev/install.sh | sh -s -- --method standalone --prefix /home/node/.local; \
|
|
254
|
+
asdf reshim nodejs; \
|
|
255
|
+
test "$(codex --version)" = "codex-cli $UAI_CODEX_CLI_VERSION"; \
|
|
256
|
+
'
|
|
257
|
+
|
|
258
|
+
# Optional agent CLIs — installed ONLY when the host has them configured
|
|
259
|
+
# (INSTALL_* build args from standard-image.ts, gated on the operator's creds
|
|
260
|
+
# and folded into the rebuild hash, so a new login rebuilds). Single-binary
|
|
261
|
+
# installs to /home/node/.{kimi-code,grok}/bin; the adapters invoke them by
|
|
262
|
+
# absolute path and task-up copies the subscription creds in. Non-fatal: a CDN
|
|
263
|
+
# hiccup must never break the image for Claude/Codex.
|
|
264
|
+
ARG INSTALL_KIMI=0
|
|
265
|
+
RUN if [ "$INSTALL_KIMI" = "1" ]; then \
|
|
266
|
+
bash -lc 'curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash' \
|
|
267
|
+
|| echo "[warn] kimi-code install failed — kimi engine unavailable in this image"; \
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
ARG INSTALL_GROK=0
|
|
271
|
+
RUN if [ "$INSTALL_GROK" = "1" ]; then \
|
|
272
|
+
bash -lc 'set -e; \
|
|
273
|
+
curl -fsSL https://x.ai/cli/install.sh | bash -s -- "$UAI_GROK_CLI_VERSION"; \
|
|
274
|
+
case "$("$HOME/.grok/bin/grok" --version)" in \
|
|
275
|
+
"grok $UAI_GROK_CLI_VERSION "*) ;; \
|
|
276
|
+
*) exit 1 ;; \
|
|
277
|
+
esac' \
|
|
278
|
+
|| { \
|
|
279
|
+
rm -rf -- "$HOME/.grok/bin" "$HOME/.grok/downloads"; \
|
|
280
|
+
echo "[warn] grok install failed — grok engine unavailable in this image"; \
|
|
281
|
+
}; \
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
# Cursor Agent (Anysphere) — installs to /home/node/.local/bin/cursor-agent.
|
|
285
|
+
# Auth is CURSOR_API_KEY (env-injected by the adapter), so nothing to copy at
|
|
286
|
+
# task-up — just the binary.
|
|
287
|
+
ARG INSTALL_CURSOR=0
|
|
288
|
+
RUN if [ "$INSTALL_CURSOR" = "1" ]; then \
|
|
289
|
+
bash -lc 'set -e; \
|
|
290
|
+
case "$(uname -m)" in \
|
|
291
|
+
x86_64|amd64) cursor_arch=x64 ;; \
|
|
292
|
+
aarch64|arm64) cursor_arch=arm64 ;; \
|
|
293
|
+
*) echo "unsupported Cursor architecture: $(uname -m)" >&2; exit 1 ;; \
|
|
294
|
+
esac; \
|
|
295
|
+
cursor_dir="$HOME/.local/share/cursor-agent/versions/$UAI_CURSOR_AGENT_VERSION"; \
|
|
296
|
+
mkdir -p "$cursor_dir" "$HOME/.local/bin"; \
|
|
297
|
+
curl -fSL "https://downloads.cursor.com/lab/$UAI_CURSOR_AGENT_VERSION/linux/$cursor_arch/agent-cli-package.tar.gz" \
|
|
298
|
+
| tar --strip-components=1 -xzf - -C "$cursor_dir"; \
|
|
299
|
+
ln -sfn "$cursor_dir/cursor-agent" "$HOME/.local/bin/agent"; \
|
|
300
|
+
ln -sfn "$cursor_dir/cursor-agent" "$HOME/.local/bin/cursor-agent"; \
|
|
301
|
+
test "$("$HOME/.local/bin/cursor-agent" --version)" = "$UAI_CURSOR_AGENT_VERSION"' \
|
|
302
|
+
|| { \
|
|
303
|
+
rm -f -- "$HOME/.local/bin/agent" "$HOME/.local/bin/cursor-agent"; \
|
|
304
|
+
rm -rf -- "$HOME/.local/share/cursor-agent/versions/$UAI_CURSOR_AGENT_VERSION"; \
|
|
305
|
+
echo "[warn] cursor install failed — cursor engine unavailable in this image"; \
|
|
306
|
+
}; \
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
# OpenCode (ADR-077) — multi-provider CLI. Installs to /home/node/.opencode/bin;
|
|
310
|
+
# the adapter invokes it by absolute path and task-up copies the provider auth
|
|
311
|
+
# (~/.local/share/opencode/auth.json) in. Non-fatal like the others.
|
|
312
|
+
ARG INSTALL_OPENCODE=0
|
|
313
|
+
RUN if [ "$INSTALL_OPENCODE" = "1" ]; then \
|
|
314
|
+
bash -lc 'curl -fsSL https://opencode.ai/install | bash' \
|
|
315
|
+
|| echo "[warn] opencode install failed — opencode engine unavailable in this image"; \
|
|
316
|
+
fi
|
|
317
|
+
|
|
318
|
+
ENV PATH=/home/node/.local/bin:$PATH
|
|
319
|
+
|
|
320
|
+
# ---------------------------------------------------------------------------
|
|
321
|
+
# 6. uai-init baked in.
|
|
322
|
+
# ---------------------------------------------------------------------------
|
|
323
|
+
|
|
324
|
+
USER root
|
|
325
|
+
|
|
326
|
+
COPY --chown=root:root container/uai-init /usr/local/bin/uai-init
|
|
327
|
+
RUN node_version="$(awk '$1 == "nodejs" { print $2; exit }' /home/node/.tool-versions)" \
|
|
328
|
+
&& test -n "$node_version" \
|
|
329
|
+
&& test -x "/opt/asdf-data/installs/nodejs/$node_version/bin/node" \
|
|
330
|
+
&& ln -s "/opt/asdf-data/installs/nodejs/$node_version/bin/node" /usr/local/bin/node \
|
|
331
|
+
&& chmod 0755 /usr/local/bin/uai-init
|
|
332
|
+
|
|
333
|
+
# Curated code-server defaults. uai-init seeds these into the per-task User
|
|
334
|
+
# settings dir when none exists (slim chrome, telemetry off, trust off).
|
|
335
|
+
COPY --chown=root:root container/code-server-settings.json /usr/local/share/uai/code-server-settings.json
|
|
336
|
+
|
|
337
|
+
# ---------------------------------------------------------------------------
|
|
338
|
+
# 7. Default working directory + entrypoint.
|
|
339
|
+
#
|
|
340
|
+
# The container is a long-lived workbench. uai-init (deps + code-server) and
|
|
341
|
+
# the agent CLIs are started by the host via `docker exec` after task-up.
|
|
342
|
+
# ---------------------------------------------------------------------------
|
|
343
|
+
|
|
344
|
+
USER node
|
|
345
|
+
WORKDIR /workspace
|
|
346
|
+
|
|
347
|
+
CMD ["sleep", "infinity"]
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Moondeck standard image
|
|
2
|
+
|
|
3
|
+
The single per-host workbench image, built once at host startup and cached as
|
|
4
|
+
`moondeck-standard:dev` (ADR-022). Every task reuses it unchanged unless a selected
|
|
5
|
+
project declares an [`extra`](../../../docs/runtime.md#the-extra-escape-hatch)
|
|
6
|
+
snippet, in which case task-up derives `uai-task-<id>` FROM this image.
|
|
7
|
+
|
|
8
|
+
See [`docs/runtime.md`](../../../docs/runtime.md) for the authoritative spec.
|
|
9
|
+
|
|
10
|
+
## What's inside
|
|
11
|
+
|
|
12
|
+
- **Base**: `debian:bookworm-slim`, non-root `node` user (home `/home/node`,
|
|
13
|
+
uid/gid 1000). The container runs as `node`; uai-init and the agent CLIs
|
|
14
|
+
expect `/home/node`.
|
|
15
|
+
- **asdf** at `/opt/asdf`, data dir `/opt/asdf-data` (the host-wide
|
|
16
|
+
`moondeck-asdf-data` volume mounts there read-only in task apps). Plugins
|
|
17
|
+
pre-added for `nodejs`, `python`, `golang`, `ruby`, `rust`. Only the root
|
|
18
|
+
defaults are installed at build time; after worktree resolution, a
|
|
19
|
+
host-controlled pre-start maintenance container is the bounded, locked
|
|
20
|
+
read-write user that installs and proves other versions before the task app
|
|
21
|
+
exists.
|
|
22
|
+
- **Default `~/.tool-versions`** (asdf's global fallback, at `/home/node`)
|
|
23
|
+
pinning a default Node + Python. The `tool-versions` file in this directory is
|
|
24
|
+
the authoritative source for the numbers; the `Dockerfile` `COPY`s it to
|
|
25
|
+
`/home/node/.tool-versions` and installs exactly those versions at build time.
|
|
26
|
+
This guarantees the agent CLIs and a 0-project scratchpad always have a
|
|
27
|
+
runtime. asdf uses its default `.tool-versions` filename and walks UP from the
|
|
28
|
+
cwd, so a project's `/workspace/<slug>/.tool-versions` **takes precedence**;
|
|
29
|
+
the home file is only the fallback. (Do **not** set
|
|
30
|
+
`ASDF_DEFAULT_TOOL_VERSIONS_FILENAME` to an absolute path — that overrides the
|
|
31
|
+
search filename and makes asdf ignore per-project files.)
|
|
32
|
+
- **uv uses asdf's Python.** For Python folders, `uai-init` runs
|
|
33
|
+
`uv venv --python "$(asdf which python)"` so the venv matches the asdf-pinned
|
|
34
|
+
version (`.tool-versions`) rather than uv's own `.python-version` /
|
|
35
|
+
`requires-python` discovery, which can disagree or be junk.
|
|
36
|
+
- **Claude Code** + **Codex**, installed globally via the root-default Node
|
|
37
|
+
(`@anthropic-ai/claude-code`, `@openai/codex`), reshimmed onto the asdf
|
|
38
|
+
shims dir so `claude` / `codex` resolve under a bare `docker exec`.
|
|
39
|
+
- **code-server** (the Editor pane), **gh**, **git**, **curl**,
|
|
40
|
+
**ca-certificates**, **jq**, **ripgrep**, **tmux**, **direnv**, **uv**,
|
|
41
|
+
plus `build-essential` and the `-dev` libraries asdf's build plugins need.
|
|
42
|
+
- **`uai-materialize-runtimes`** baked at
|
|
43
|
+
`/usr/local/bin/uai-materialize-runtimes`; it is the credential-free,
|
|
44
|
+
workspace-read-only shared-cache writer used before Compose starts a task.
|
|
45
|
+
The host supplies the selected project slugs at the fixed, read-only
|
|
46
|
+
`/run/uai/runtime-projects` allowlist; only those immediate Git worktrees are
|
|
47
|
+
inspected, so workspace control/shared directories cannot become projects.
|
|
48
|
+
- **`uai-init`** baked at `/usr/local/bin/uai-init`; it installs task-local
|
|
49
|
+
dependencies and starts code-server, but does not mutate asdf runtimes.
|
|
50
|
+
- **Task-private npm globals** under `/home/node/.local/share/uai-npm`, with
|
|
51
|
+
cache/logs under `/home/node/.cache/uai-npm`. The shared asdf shims and
|
|
52
|
+
system controls remain ahead of this prefix's `bin` on `PATH`, and
|
|
53
|
+
`ASDF_SKIP_RESHIM=1` prevents npm's asdf hook from writing shared shims.
|
|
54
|
+
- `WORKDIR /workspace`; `CMD ["sleep","infinity"]`.
|
|
55
|
+
|
|
56
|
+
Codex credentials are **not** baked in or mounted — task-up `docker cp`s them
|
|
57
|
+
into `/home/node/.codex` at init (ADR-015 keeps secrets host-side). `~/.claude`
|
|
58
|
+
is bind-mounted read-only by the generated compose.
|
|
59
|
+
|
|
60
|
+
## Pinned constants (must match the rest of the host runtime)
|
|
61
|
+
|
|
62
|
+
| Thing | Value |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| Standard image tag | `moondeck-standard:dev` |
|
|
65
|
+
| Per-task derived image (only with `extra`) | `uai-task-<taskId>` |
|
|
66
|
+
| Shared asdf data volume | app: `moondeck-asdf-data:/opt/asdf-data:ro`; maintenance: `:rw` |
|
|
67
|
+
| Task npm prefix / cache | `/home/node/.local/share/uai-npm` / `/home/node/.cache/uai-npm` |
|
|
68
|
+
| Compose service name | `app` (container `task-<taskId>-app-1`) |
|
|
69
|
+
| In-container workspace | `/workspace` |
|
|
70
|
+
| code-server port | `8080` (inside the container) |
|
|
71
|
+
|
|
72
|
+
## Build
|
|
73
|
+
|
|
74
|
+
The host process builds this at startup when the cached digest is missing or
|
|
75
|
+
stale. To build by hand from the repo root:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
docker build -t moondeck-standard:dev host-agent/images/standard
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
A derived per-task image (when a project has `extra`) is built by `task-up`
|
|
82
|
+
from a generated `.uai/Dockerfile` that is `FROM moondeck-standard:dev` plus the
|
|
83
|
+
concatenated `extra` RUN lines, tagged `uai-task-<id>`.
|
|
84
|
+
|
|
85
|
+
## Smoke test (no uai involved)
|
|
86
|
+
|
|
87
|
+
These files are validated at the Phase E smoke test (no Docker in CI), so the
|
|
88
|
+
steps below are for local image iteration only.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# 1. Build.
|
|
92
|
+
docker build -t moondeck-standard:dev host-agent/images/standard
|
|
93
|
+
|
|
94
|
+
# 2. Create the shared asdf data volume + a throwaway Git worktree and the
|
|
95
|
+
# host-controlled selected-project allowlist.
|
|
96
|
+
docker volume create moondeck-asdf-data
|
|
97
|
+
mkdir -p /tmp/uai-smoke/workspace/demo/.git
|
|
98
|
+
printf 'nodejs 22.11.0\n' > /tmp/uai-smoke/workspace/demo/.tool-versions
|
|
99
|
+
printf '{"name":"demo","packageManager":"pnpm@10.15.0"}\n' \
|
|
100
|
+
> /tmp/uai-smoke/workspace/demo/package.json
|
|
101
|
+
printf 'demo\n' > /tmp/uai-smoke/runtime-projects
|
|
102
|
+
|
|
103
|
+
# 3. Materialize before the app exists, the way task-up does.
|
|
104
|
+
docker run --rm --user node --workdir /workspace \
|
|
105
|
+
-v /tmp/uai-smoke/workspace:/workspace:ro \
|
|
106
|
+
-v /tmp/uai-smoke/runtime-projects:/run/uai/runtime-projects:ro \
|
|
107
|
+
-v moondeck-asdf-data:/opt/asdf-data:rw \
|
|
108
|
+
moondeck-standard:dev /usr/bin/timeout --signal=TERM --kill-after=10s 600s \
|
|
109
|
+
/usr/local/bin/uai-materialize-runtimes
|
|
110
|
+
|
|
111
|
+
# 4. Run the container the way compose would (service `app`, workspace bind,
|
|
112
|
+
# asdf cache volume, code-server port published on loopback).
|
|
113
|
+
docker run -d --name uai-smoke \
|
|
114
|
+
-v /tmp/uai-smoke/workspace:/workspace \
|
|
115
|
+
-v /tmp/uai-smoke/runtime-projects:/run/uai/runtime-projects:ro \
|
|
116
|
+
-v moondeck-asdf-data:/opt/asdf-data:ro \
|
|
117
|
+
-e ASDF_SKIP_RESHIM=1 \
|
|
118
|
+
-e NPM_CONFIG_PREFIX=/home/node/.local/share/uai-npm \
|
|
119
|
+
-e npm_config_prefix=/home/node/.local/share/uai-npm \
|
|
120
|
+
-e NPM_CONFIG_CACHE=/home/node/.cache/uai-npm \
|
|
121
|
+
-e npm_config_cache=/home/node/.cache/uai-npm \
|
|
122
|
+
-e NPM_CONFIG_LOGS_DIR=/home/node/.cache/uai-npm/_logs \
|
|
123
|
+
-e npm_config_logs_dir=/home/node/.cache/uai-npm/_logs \
|
|
124
|
+
-e NPM_CONFIG_UMASK=0002 -e npm_config_umask=0002 \
|
|
125
|
+
-e 'PATH=/opt/asdf-data/shims:/opt/asdf/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/node/.local/share/uai-npm/bin' \
|
|
126
|
+
-p 127.0.0.1::8080 \
|
|
127
|
+
moondeck-standard:dev
|
|
128
|
+
|
|
129
|
+
# 5. Prove isolation and sanity-check the baked toolchain (bare exec — no
|
|
130
|
+
# profile sourced).
|
|
131
|
+
docker exec uai-smoke sh -c \
|
|
132
|
+
'! touch /opt/asdf-data/.uai-app-write-probe 2>/dev/null && test "$(npm config get prefix)" = /home/node/.local/share/uai-npm'
|
|
133
|
+
docker exec uai-smoke bash -lc 'asdf --version && node -v && python --version'
|
|
134
|
+
docker exec uai-smoke claude --version
|
|
135
|
+
docker exec uai-smoke codex --version
|
|
136
|
+
|
|
137
|
+
# 6. Run uai-init: task-local deps + code-server on 8080.
|
|
138
|
+
docker exec uai-smoke /usr/local/bin/uai-init
|
|
139
|
+
docker exec uai-smoke bash -lc 'curl -sf http://127.0.0.1:8080/healthz || \
|
|
140
|
+
curl -sf -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8080/'
|
|
141
|
+
|
|
142
|
+
# 7. Tear down.
|
|
143
|
+
docker rm -f uai-smoke
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Expected: step 5 proves the app cannot write the shared cache and npm resolves
|
|
147
|
+
the private prefix, then prints the asdf version and root-default Node +
|
|
148
|
+
Python; `claude` / `codex` resolve; step 6 launches code-server bound to
|
|
149
|
+
`0.0.0.0:8080` and the HTTP probe returns a 2xx/3xx.
|
|
150
|
+
|
|
151
|
+
## Notes / gotchas
|
|
152
|
+
|
|
153
|
+
- **Bash 3.2 constraint does NOT apply here.** `uai-init` runs on the Linux
|
|
154
|
+
base image (bash 5) and may use `shopt`, arrays, etc. The 3.2 constraint is
|
|
155
|
+
only for host scripts under `host-agent/scripts/agent/`.
|
|
156
|
+
- **First-use asdf cost.** The first task needing a given runtime version pays
|
|
157
|
+
the download/build time; results persist on `moondeck-asdf-data` for every later
|
|
158
|
+
task on the host.
|
|
159
|
+
- **Rolling recovery is preserve-and-recreate.** Before shared maintenance, an
|
|
160
|
+
upgraded host stops legacy writable/non-current apps and leaves their
|
|
161
|
+
worktrees intact. Docker mount modes are immutable, so only an exact current
|
|
162
|
+
read-only app can recover in place; explicit Resume recreates anything else.
|
|
163
|
+
- **Ordinary `uai-init` failures remain best-effort.** A failing task-local
|
|
164
|
+
dependency install logs and is skipped so one bad repo never blocks editor
|
|
165
|
+
startup. Runtime materialization is separate and fail-closed before the app
|
|
166
|
+
starts; a workspace runtime-authority violation exits 78 so the host can
|
|
167
|
+
quarantine the container instead of treating it as ready.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"//": "Moondeck — slimmed code-server defaults. Seeded into the User settings",
|
|
3
|
+
"//2": "dir by uai-init when none exists yet. Trims welcome/telemetry/chat",
|
|
4
|
+
"//3": "chrome so the Editor pane is a focused editor, not a full AI-IDE.",
|
|
5
|
+
|
|
6
|
+
"telemetry.telemetryLevel": "off",
|
|
7
|
+
"workbench.startupEditor": "none",
|
|
8
|
+
"workbench.tips.enabled": false,
|
|
9
|
+
"workbench.welcomePage.walkthroughs.openOnInstall": false,
|
|
10
|
+
"window.commandCenter": false,
|
|
11
|
+
"chat.commandCenter.enabled": false,
|
|
12
|
+
"chat.viewSessions.enabled": false,
|
|
13
|
+
"chat.disableAIFeatures": true,
|
|
14
|
+
"update.mode": "none",
|
|
15
|
+
"extensions.autoCheckUpdates": false,
|
|
16
|
+
"extensions.autoUpdate": false,
|
|
17
|
+
"git.openRepositoryInParentFolders": "always",
|
|
18
|
+
"workbench.secondarySideBar.defaultVisibility": "hidden",
|
|
19
|
+
|
|
20
|
+
"//4": "Colour theme. Overwritten at FIRST init by uai-init when the task",
|
|
21
|
+
"//5": "carries UAI_EDITOR_THEME (ADR-091); this value is the fallback for",
|
|
22
|
+
"//6": "tasks created before that shipped, or created outside a browser.",
|
|
23
|
+
"workbench.colorTheme": "Dark 2026",
|
|
24
|
+
|
|
25
|
+
"//7": "An editor, not an IDE (ADR-092). This pane is one of three in a",
|
|
26
|
+
"//8": "task's right panel and is often half a screen wide, so every bar it",
|
|
27
|
+
"//9": "does not need is width the code does. Cut here is chrome only: the",
|
|
28
|
+
"//10": "status bar, the layout buttons, the menu bar, the empty-editor",
|
|
29
|
+
"//11": "watermark, Open Editors, the minimap and the terminal's chat hint.",
|
|
30
|
+
"//12": "Breadcrumbs STAY: with the status bar gone they are the only thing",
|
|
31
|
+
"//13": "naming the path, and a tab reading 'route.ts' is worth little in a",
|
|
32
|
+
"//14": "repo with eleven of them.",
|
|
33
|
+
"workbench.statusBar.visible": false,
|
|
34
|
+
"workbench.layoutControl.enabled": false,
|
|
35
|
+
"window.menuBarVisibility": "hidden",
|
|
36
|
+
"workbench.editor.empty.hint": "hidden",
|
|
37
|
+
"explorer.openEditors.visible": 0,
|
|
38
|
+
"editor.minimap.enabled": false,
|
|
39
|
+
"terminal.integrated.initialHint": false,
|
|
40
|
+
|
|
41
|
+
"//15": "Hide the activity bar and its custom title strip. Explorer stays",
|
|
42
|
+
"//16": "open; the Editor pane's own toolbar provides the pointer route to",
|
|
43
|
+
"//17": "Terminal. Individual pins still live in the BROWSER's IndexedDB; the",
|
|
44
|
+
"//18": "guarded migration keeps the requested containers ready if a user",
|
|
45
|
+
"//19": "later restores the bar.",
|
|
46
|
+
"workbench.activityBar.location": "hidden",
|
|
47
|
+
|
|
48
|
+
"//22": "Workspace Trust — every task runs in an isolated container off the",
|
|
49
|
+
"//23": "user's own repos. The 'do you trust this folder?' prompt is pure",
|
|
50
|
+
"//24": "friction here; the user already trusts their code and the container",
|
|
51
|
+
"//25": "is the security boundary.",
|
|
52
|
+
"security.workspace.trust.enabled": false,
|
|
53
|
+
|
|
54
|
+
"//26": "Port forwarding — code-server's own auto-forward / 'Open in Browser'",
|
|
55
|
+
"//27": "popup can't reach anything useful here: dev servers are exposed",
|
|
56
|
+
"//28": "through Moondeck's preview tunnel (ADR-025), not code-server's forwarder.",
|
|
57
|
+
"//29": "Disable it so the popup doesn't mislead.",
|
|
58
|
+
"remote.autoForwardPorts": false,
|
|
59
|
+
"remote.restoreForwardedPorts": false,
|
|
60
|
+
|
|
61
|
+
"//30": "Integrated terminal uses zsh + oh-my-zsh (the node login shell).",
|
|
62
|
+
"terminal.integrated.defaultProfile.linux": "zsh"
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.33.0
|