@exagone313/dsh-podman 0.2.0-rc.2

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.
Files changed (165) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +41 -0
  3. package/README.zh.md +41 -0
  4. package/cordis.patch.yml +17 -0
  5. package/dist/approval-reasons.d.ts +129 -0
  6. package/dist/approval-reasons.js +253 -0
  7. package/dist/approval-reasons.test.d.ts +1 -0
  8. package/dist/approval-reasons.test.js +115 -0
  9. package/dist/approval.d.ts +29 -0
  10. package/dist/approval.js +412 -0
  11. package/dist/approval.test.d.ts +1 -0
  12. package/dist/approval.test.js +462 -0
  13. package/dist/card-route.d.ts +15 -0
  14. package/dist/card-route.js +478 -0
  15. package/dist/card-route.test.d.ts +1 -0
  16. package/dist/card-route.test.js +181 -0
  17. package/dist/card-test-support.d.ts +15 -0
  18. package/dist/card-test-support.js +160 -0
  19. package/dist/client/ContainerCard.d.ts +6 -0
  20. package/dist/client/ContainerCard.js +79 -0
  21. package/dist/client/card-client.d.ts +6 -0
  22. package/dist/client/card-client.js +47 -0
  23. package/dist/client/card-protocol.d.ts +101 -0
  24. package/dist/client/card-protocol.js +10 -0
  25. package/dist/client/container-card-caches.d.ts +10 -0
  26. package/dist/client/container-card-caches.js +31 -0
  27. package/dist/client/container-card-controller.d.ts +107 -0
  28. package/dist/client/container-card-controller.js +207 -0
  29. package/dist/client/container-card-create-modal.d.ts +24 -0
  30. package/dist/client/container-card-create-modal.js +109 -0
  31. package/dist/client/container-card-directory-styles.d.ts +5 -0
  32. package/dist/client/container-card-directory-styles.js +246 -0
  33. package/dist/client/container-card-directory.d.ts +12 -0
  34. package/dist/client/container-card-directory.js +144 -0
  35. package/dist/client/container-card-editors.d.ts +30 -0
  36. package/dist/client/container-card-editors.js +116 -0
  37. package/dist/client/container-card-images.d.ts +37 -0
  38. package/dist/client/container-card-images.js +35 -0
  39. package/dist/client/container-card-paths.d.ts +20 -0
  40. package/dist/client/container-card-paths.js +88 -0
  41. package/dist/client/container-card-row.d.ts +30 -0
  42. package/dist/client/container-card-row.js +54 -0
  43. package/dist/client/container-card-secrets.d.ts +21 -0
  44. package/dist/client/container-card-secrets.js +71 -0
  45. package/dist/client/container-card-shared.d.ts +55 -0
  46. package/dist/client/container-card-shared.js +152 -0
  47. package/dist/client/container-card-styles.d.ts +21 -0
  48. package/dist/client/container-card-styles.js +143 -0
  49. package/dist/client/container-card-volumes.d.ts +12 -0
  50. package/dist/client/container-card-volumes.js +37 -0
  51. package/dist/client/container-card-workspace.d.ts +31 -0
  52. package/dist/client/container-card-workspace.js +24 -0
  53. package/dist/client/directory-picker.d.ts +12 -0
  54. package/dist/client/directory-picker.js +30 -0
  55. package/dist/client/index.d.ts +5 -0
  56. package/dist/client/index.js +4419 -0
  57. package/dist/client/locales.d.ts +9 -0
  58. package/dist/client/locales.js +370 -0
  59. package/dist/client/read-only-approval.d.ts +8 -0
  60. package/dist/client/read-only-approval.js +63 -0
  61. package/dist/client/slot-contract.d.ts +12 -0
  62. package/dist/client/slot-contract.js +4 -0
  63. package/dist/client/terminal-styles.d.ts +5 -0
  64. package/dist/client/terminal-styles.js +31 -0
  65. package/dist/client/tool-views.d.ts +8 -0
  66. package/dist/client/tool-views.js +241 -0
  67. package/dist/containers.test.d.ts +1 -0
  68. package/dist/containers.test.js +443 -0
  69. package/dist/daemons.test.d.ts +1 -0
  70. package/dist/daemons.test.js +153 -0
  71. package/dist/fs-provider.d.ts +23 -0
  72. package/dist/fs-provider.js +184 -0
  73. package/dist/fs-provider.test.d.ts +1 -0
  74. package/dist/fs-provider.test.js +260 -0
  75. package/dist/generated/version.d.ts +2 -0
  76. package/dist/generated/version.js +6 -0
  77. package/dist/grpc/proto/dshctl/v1/control.proto +133 -0
  78. package/dist/grpc/proto/dshguest/v1/guest.proto +79 -0
  79. package/dist/grpc/runtime-client.d.ts +5 -0
  80. package/dist/grpc/runtime-client.js +43 -0
  81. package/dist/guest-rpc.d.ts +62 -0
  82. package/dist/guest-rpc.js +391 -0
  83. package/dist/images.test.d.ts +1 -0
  84. package/dist/images.test.js +79 -0
  85. package/dist/index.d.ts +22 -0
  86. package/dist/index.js +140 -0
  87. package/dist/misc.test.d.ts +1 -0
  88. package/dist/misc.test.js +141 -0
  89. package/dist/mount-enums.d.ts +22 -0
  90. package/dist/mount-enums.js +81 -0
  91. package/dist/mount-input.d.ts +14 -0
  92. package/dist/mount-input.js +64 -0
  93. package/dist/mounts.test.d.ts +1 -0
  94. package/dist/mounts.test.js +408 -0
  95. package/dist/output-reader.d.ts +34 -0
  96. package/dist/output-reader.js +87 -0
  97. package/dist/paths.test.d.ts +1 -0
  98. package/dist/paths.test.js +74 -0
  99. package/dist/preferences.d.ts +6 -0
  100. package/dist/preferences.js +27 -0
  101. package/dist/project-path.d.ts +6 -0
  102. package/dist/project-path.js +73 -0
  103. package/dist/project-path.test.d.ts +1 -0
  104. package/dist/project-path.test.js +52 -0
  105. package/dist/prompts.d.ts +13 -0
  106. package/dist/prompts.js +149 -0
  107. package/dist/public.d.ts +4 -0
  108. package/dist/public.js +63 -0
  109. package/dist/read-only-shell.d.ts +14 -0
  110. package/dist/read-only-shell.js +150 -0
  111. package/dist/read-only-shell.test.d.ts +1 -0
  112. package/dist/read-only-shell.test.js +127 -0
  113. package/dist/secrets.test.d.ts +1 -0
  114. package/dist/secrets.test.js +181 -0
  115. package/dist/settings-commands.test.d.ts +1 -0
  116. package/dist/settings-commands.test.js +549 -0
  117. package/dist/settings-create.test.d.ts +1 -0
  118. package/dist/settings-create.test.js +394 -0
  119. package/dist/settings-mounts.test.d.ts +1 -0
  120. package/dist/settings-mounts.test.js +467 -0
  121. package/dist/settings-paths.test.d.ts +1 -0
  122. package/dist/settings-paths.test.js +65 -0
  123. package/dist/settings-schema.d.ts +14 -0
  124. package/dist/settings-schema.js +14 -0
  125. package/dist/settings-workspaces.test.d.ts +1 -0
  126. package/dist/settings-workspaces.test.js +265 -0
  127. package/dist/spill-store.d.ts +17 -0
  128. package/dist/spill-store.js +54 -0
  129. package/dist/spill-store.test.d.ts +1 -0
  130. package/dist/spill-store.test.js +59 -0
  131. package/dist/subprocess.d.ts +9 -0
  132. package/dist/subprocess.js +369 -0
  133. package/dist/subprocess.test.d.ts +1 -0
  134. package/dist/subprocess.test.js +234 -0
  135. package/dist/test-support.d.ts +328 -0
  136. package/dist/test-support.js +437 -0
  137. package/dist/tool-defs.d.ts +26 -0
  138. package/dist/tool-defs.js +188 -0
  139. package/dist/tool-handlers.d.ts +2 -0
  140. package/dist/tool-handlers.js +546 -0
  141. package/dist/tool-params.d.ts +803 -0
  142. package/dist/tool-params.js +494 -0
  143. package/dist/tool-schemas.d.ts +2 -0
  144. package/dist/tool-schemas.js +5 -0
  145. package/dist/tool-views.d.ts +2 -0
  146. package/dist/tool-views.js +56 -0
  147. package/dist/views.test.d.ts +1 -0
  148. package/dist/views.test.js +23 -0
  149. package/dist/volumes.test.d.ts +1 -0
  150. package/dist/volumes.test.js +40 -0
  151. package/dist/workspace-binding.d.ts +40 -0
  152. package/dist/workspace-binding.js +275 -0
  153. package/dist/workspace-binding.test.d.ts +1 -0
  154. package/dist/workspace-binding.test.js +337 -0
  155. package/docs/architecture.md +173 -0
  156. package/docs/architecture.zh.md +129 -0
  157. package/docs/configuration.md +160 -0
  158. package/docs/configuration.zh.md +147 -0
  159. package/docs/development.md +142 -0
  160. package/docs/development.zh.md +135 -0
  161. package/docs/install-dsh-and-dsh-podman.md +165 -0
  162. package/docs/install-dsh-and-dsh-podman.zh.md +145 -0
  163. package/docs/usage.md +393 -0
  164. package/docs/usage.zh.md +315 -0
  165. package/package.json +119 -0
@@ -0,0 +1,173 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
3
+
4
+ SPDX-License-Identifier: MIT
5
+ -->
6
+
7
+ # Architecture
8
+
9
+ dsh-podman has three components:
10
+
11
+ | Component | Runs | What it does |
12
+ | ------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------ |
13
+ | `@exagone313/dsh-podman` | Inside dsh itself | Registers `ctx.subprocess` and `ctx.fs` backed by the orchestrator |
14
+ | `dsh-podman-guest-agent` | Inside every guest container | Serves the exec/filesystem gRPC API for one workspace |
15
+ | `dsh-podman-orchestrator` | A container with access to the Podman API | Owns the control socket and persisted state; creates/removes guest containers; builds workspace images |
16
+
17
+ The plugin auto-creates a missing workspace using its configured default image
18
+ and a single read-write project mount; `container_mount_update` can remount that
19
+ mount read-only. It never falls back to host execution.
20
+
21
+ ## Control plane
22
+
23
+ The plugin connects to the orchestrator over a Unix socket at
24
+ `<socketsRoot>/orchestrator.sock` and talks gRPC. Requests carry the shared
25
+ token (`DSH_PODMAN_ORCHESTRATOR_TOKEN`) as an `authorization: bearer` header.
26
+
27
+ The orchestrator service exposes these gRPC methods (backing both the UI and the
28
+ tools): `ListContainers` (returns only the guest containers the orchestrator
29
+ created — containers it does not own are never exposed),
30
+ `StartContainer{workspace_slug, container, image_id, mounts, env, secret_env,
31
+ paths}`
32
+ (creates or replaces a container in the workspace's pod; an empty `container`
33
+ targets the default container and other names are validated),
34
+ `RecreateContainer{workspace_slug, container, image_id, mounts, env, paths}`
35
+ (stops, removes, and recreates a container, optionally with a new image, project
36
+ mounts, environment, or PATH additions; an empty `image_id` keeps the
37
+ workspace's current image), `RemoveContainer`, `AddContainerMount`,
38
+ `RemoveContainerMount`, `SetContainerPaths`, `AddContainerSecret`, and
39
+ `RemoveContainerSecret`.
40
+
41
+ ## Workspaces and pods
42
+
43
+ Each workspace maps to a **podman pod** (`dsh-podman-<slug>`, where `<slug>` is
44
+ the workspace UUID) so its containers share a network namespace. Every workspace
45
+ has a **default container** (`dsh-podman-<slug>-default`); additional, named
46
+ containers (`dsh-podman-<slug>-<name>`) can be created inside the same pod.
47
+ Containers' root filesystems are mounted read-only, with podman's read-write
48
+ tmpfs on `/tmp`, `/var/tmp`, and `/run` (and `/dev` and `/dev/shm` left
49
+ writable). All other writable state lives in the project bind mount, named
50
+ volumes, or tmpfs mounts. `/tmp` and `/var/tmp` are reachable through the guest
51
+ file API, and the guest agent spills oversized command output under
52
+ `/tmp/dsh-podman`. Oversized tool results are spilled there too, under
53
+ `/tmp/dsh-podman/spill/<session>/`, by the plugin's `ctx.spillStore` — so the
54
+ model reads the artifact back with the container file tools instead of a host
55
+ path the container cannot reach.
56
+
57
+ A workspace's pod is torn down when its last container is removed, or directly
58
+ through `RemoveWorkspace` (the settings card's **Remove pod** action), which
59
+ stops the containers' daemons, removes the pod and its containers, cleans their
60
+ socket directories, and drops the stored workspace. Volumes, secrets, and
61
+ project data are left untouched.
62
+
63
+ Beyond project mounts, a container can mount named volumes (prefixed
64
+ `DSH_PODMAN_VOLUME_PREFIX`, default `dsh-podman-`, and auto-created by podman on
65
+ first use), secrets, or tmpfs at arbitrary container paths — except where they
66
+ would overlap a reserved path:
67
+
68
+ - `DSH_PODMAN_PROJECTS_ROOT`, reserved for project mounts;
69
+ - `DSH_PODMAN_SOCKETS_ROOT`, which carries the guest agent's socket;
70
+ - `DSH_PODMAN_GUEST_AGENT_IMAGE_MOUNT`, which the container runs its entry point
71
+ from;
72
+ - `/tmp`, which podman mounts as the read-write tmpfs the guest file API reaches
73
+ and where command output is spilled.
74
+
75
+ A destination that contains a reserved path is refused as well as one that sits
76
+ inside it, since it would hide everything beneath it.
77
+
78
+ Commands run through the guest exec API get `/dev/null` on stdin unless the
79
+ caller explicitly asks for a pipe, so a command that reads stdin sees EOF
80
+ instead of blocking (a bare `rg`/`grep` with no path therefore searches the
81
+ working directory rather than reading an empty pipe). `container_glob` and
82
+ `container_grep` also pass their resolved path to ripgrep as the search
83
+ directory, never as a `--glob` pattern (a path containing `/` would never match
84
+ one), and a ripgrep failure (exit code 2) is reported as a tool error rather
85
+ than an empty result. Ripgrep anchors a `--glob` pattern containing `/` to the
86
+ process working directory, so a discovery listing the harness starts with an
87
+ absolute search root runs from that root: `glob`'s `pattern` then anchors to its
88
+ `path` while the printed paths stay absolute.
89
+
90
+ Project mounts resolve through symlinks and are confined to the projects root,
91
+ so a symlink inside a writable project cannot redirect the bind mount to a path
92
+ outside it. Symlinks with absolute targets are never followed; name the other
93
+ project directly instead.
94
+
95
+ ## Guest agent and daemons
96
+
97
+ The orchestrator starts the guest agent inside each container over the
98
+ container's socket directory. The guest agent serves the exec/filesystem gRPC
99
+ API for that workspace, runs and supervises background **daemons**, and reads
100
+ and writes files. Each guest container gets exactly one socket directory
101
+ bind-mounted into it, so a guest never sees the orchestrator's control socket
102
+ nor any other workspace's socket directory.
103
+
104
+ Commands and daemons inherit the guest agent's environment, minus the reserved
105
+ `DSH_PODMAN` namespace: the agent's own token stays with the agent instead of
106
+ being copied into everything it starts. A daemon started with `inheritEnv=false`
107
+ instead receives only the `PATH`/`HOME` baseline plus its own `env`.
108
+
109
+ Each container also carries ordered **PATH additions**. The orchestrator
110
+ persists them (`SetContainerPaths`) and hands them to the guest agent as
111
+ `DSH_PODMAN_GUEST_PATHS` on every create or recreate; the agent holds them in
112
+ memory (`SetPaths`/`GetPaths` over its gRPC API) and prepends them to the PATH
113
+ of every process it starts, including the plugin's `ctx.subprocess` provider, so
114
+ the built-in shell and filesystem tools see them too. Changing the list does not
115
+ recreate the container, so daemons started earlier keep their old PATH.
116
+
117
+ Under `read-only` permission the harness sandbox cannot confine commands (the
118
+ plugin replaces `ctx.subprocess`/`ctx.fs` and unwraps the `landlock-run`
119
+ wrapper), so the plugin enforces the mode itself: a shell or file tool runs only
120
+ when every mount that carries a mode is `read_only`. Otherwise the plugin asks
121
+ through the approval service under the reserved tool name
122
+ `dsh_podman_builtin_remount_read_only` — which the browser half renders with its
123
+ own **Remount & run** labels — and, on approval, recreates the container with
124
+ the read-write mounts forced read-only before running the tool. The harness's
125
+ `sandbox_permissions` escalation is granted without prompting for the same
126
+ reason: the widening it asks for has no effect here.
127
+
128
+ Recreating a container or shutting down the orchestrator first asks the
129
+ container's guest agent to gracefully stop its daemons (SIGTERM, ~10s grace)
130
+ before podman tears the container down.
131
+
132
+ ## Images
133
+
134
+ The orchestrator builds workspace images through Podman (see
135
+ [Usage](usage.md#image-model) for the primitive/base/custom tiers). Base images
136
+ are built locally from their primitive reference (or pulled when
137
+ `DSH_PODMAN_BASE_IMAGE_PREFIX` points at a registry) without any guest-agent
138
+ binary. The guest agent is provided at container creation by mounting its own
139
+ image read-only into the container (see [Configuration](configuration.md)).
140
+ Custom images are layered on top of a parent image and add extra packages. Every
141
+ build streams its context to the Podman API as a tar holding the generated
142
+ Containerfile, so a build writes nothing to the orchestrator's state directory.
143
+
144
+ `image_rebuild_all` first ensures every base image (building locally or pulling
145
+ from a public registry per `DSH_PODMAN_BASE_IMAGE_PREFIX`), then rebuilds the
146
+ stored custom images **in dependency order** — each parent before the images
147
+ derived from it. An image whose rebuild fails, and every image that depends on
148
+ it, is reported in `skipped` while the rest continue.
149
+
150
+ When a host cache is configured (`DSH_PODMAN_HOST_*_CACHE`, mounted into both
151
+ the build container and the orchestrator), builds reuse downloaded packages. The
152
+ settings card reports each cache's size and can clean it — keep the newest
153
+ version of every package, or empty the cache. The builder serializes a cleanup
154
+ against builds with a read/write lock, so a cleanup never deletes a package out
155
+ from under a running build.
156
+
157
+ ## Settings card transport
158
+
159
+ The card talks to the host over one authenticated fetch route below the harness
160
+ API path (`/api/podman/card`), registered on the connection service so the
161
+ carrier applies its Host/Origin fence and browser authentication first:
162
+
163
+ - `GET` returns the live orchestrator snapshot (containers, images, workspaces,
164
+ volumes, secrets, caches), built fresh on every request.
165
+ - `POST` runs exactly one command (`remove` / `recreate` / `create` /
166
+ `image_rebuild` / `image_rebuild_all` / volume / secret / secret-env / mount
167
+ ops) against the orchestrator and returns the notice to show.
168
+
169
+ The settings namespace therefore holds **only real preferences**: the default
170
+ image, the sockets root, and the card's active locale (`uiLocale`, so the host
171
+ can render approval text in the session language — see
172
+ [Approval](usage.md#approval)). Nothing derived from the orchestrator is
173
+ persisted, and no command round-trips through the settings document.
@@ -0,0 +1,129 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
3
+
4
+ SPDX-License-Identifier: MIT
5
+ -->
6
+
7
+ # 架构
8
+
9
+ dsh-podman 由三个组件组成:
10
+
11
+ | 组件 | 运行位置 | 功能 |
12
+ | ------------------------- | ---------------------------- | ---------------------------------------------------------------------- |
13
+ | `@exagone313/dsh-podman` | dsh 内部 | 注册由 orchestrator 提供支撑的 `ctx.subprocess` 和 `ctx.fs` |
14
+ | `dsh-podman-guest-agent` | 每个 guest 容器内部 | 为一个工作区提供 exec/filesystem gRPC API |
15
+ | `dsh-podman-orchestrator` | 一个可访问 Podman API 的容器 | 持有 control socket 和持久化状态;创建/删除 guest 容器;构建工作区镜像 |
16
+
17
+ 该插件会自动使用其配置的默认镜像和单个读写项目挂载来创建缺失的工作区;`container_mount_update`
18
+ 可将该挂载重新挂载为只读。它绝不会回退到主机执行。
19
+
20
+ ## 控制平面
21
+
22
+ 插件通过位于 `<socketsRoot>/orchestrator.sock` 的 Unix socket 连接
23
+ orchestrator(编排器)并通信 gRPC。请求携带共享
24
+ token(`DSH_PODMAN_ORCHESTRATOR_TOKEN`)作为 `authorization: bearer` 标头。
25
+
26
+ orchestrator 服务公开以下 gRPC 方法(同时支撑 UI
27
+ 和工具):`ListContainers`(仅返回 orchestrator 创建的 guest
28
+ 容器——它不拥有的容器永远不会被暴露)、`StartContainer{workspace_slug, container, image_id, mounts, env, secret_env, paths}`(在工作区的
29
+ pod 中创建或替换容器;空的 `container`
30
+ 指向默认容器,其他名称会被校验)、`RecreateContainer{workspace_slug, container, image_id, mounts, env, paths}`(停止、删除并重新创建容器,可选择使用新镜像、项目挂载、环境或
31
+ PATH 附加项;空的 `image_id`
32
+ 保留工作区当前的镜像)、`RemoveContainer`、`AddContainerMount`、`RemoveContainerMount`、`SetContainerPaths`、`AddContainerSecret`
33
+ 和 `RemoveContainerSecret`。
34
+
35
+ ## 工作区与 pod
36
+
37
+ 每个工作区映射到一个 **podman pod**(`dsh-podman-<slug>`,其中 `<slug>` 是工作区
38
+ UUID),因此其容器共享一个网络命名空间。每个工作区都有一个
39
+ **默认容器**(`dsh-podman-<slug>-default`);可以在同一个 pod
40
+ 内创建额外的、命名的容器(`dsh-podman-<slug>-<name>`)。容器的根文件系统以只读方式挂载,`/tmp`、`/var/tmp`
41
+ 和 `/run` 上是 podman 的可写 tmpfs(`/dev` 和 `/dev/shm`
42
+ 保持可写)。所有其他可写状态都存在于项目绑定挂载、命名卷或 tmpfs 挂载中。guest
43
+ 文件 API 可以访问 `/tmp`,而 guest agent 会把超出上限的命令输出写入
44
+ `/tmp/dsh-podman` 下的溢出文件。
45
+
46
+ 工作区的 Pod 会在其最后一个容器被移除时拆除,也可直接通过
47
+ `RemoveWorkspace`(设置卡片中的 **移除 Pod**
48
+ 操作)拆除:它会停止各容器的守护进程、移除 Pod 及其容器、清理它们的 socket
49
+ 目录,并删除存储的工作区记录。卷、机密和项目数据保持不变。
50
+
51
+ 除了项目挂载之外,容器还可以在任意容器路径挂载命名卷(以
52
+ `DSH_PODMAN_VOLUME_PREFIX` 为前缀,默认 `dsh-podman-`,由 podman
53
+ 在首次使用时自动创建)、机密或 tmpfs——除非它们会与保留路径重叠:
54
+
55
+ - `DSH_PODMAN_PROJECTS_ROOT`,保留给项目挂载;
56
+ - `DSH_PODMAN_SOCKETS_ROOT`,承载 guest agent 的 socket;
57
+ - `DSH_PODMAN_GUEST_AGENT_IMAGE_MOUNT`,容器从其运行入口点;
58
+ - `/tmp`,podman 在其中挂载 guest 文件 API 可访问的可写
59
+ tmpfs,命令输出也溢出写入其中。
60
+
61
+ 包含保留路径的目标会被拒绝,位于其内部的目标同样会被拒绝,因为它会隐藏其下的所有内容。
62
+
63
+ 项目挂载会解析符号链接并被限制在 projects root
64
+ 内,因此可写项目中的符号链接不能将绑定挂载重定向到其外部的路径。带有绝对目标的符号链接永远不会被跟随;请直接命名另一个项目。
65
+
66
+ ## Guest agent 与守护进程
67
+
68
+ orchestrator 通过容器的套接字目录在每个容器内启动 guest agent(来宾代理)。guest
69
+ agent 为该工作区提供 exec/filesystem gRPC
70
+ API,运行并监督后台**守护进程**,并读取和写入文件。每个 guest
71
+ 容器恰好获得一个绑定挂载到其中的套接字目录,因此 guest 永远不会看到 orchestrator
72
+ 的 control socket,也不会看到任何其他工作区的套接字目录。
73
+
74
+ 命令和守护进程继承 guest agent 的环境,减去保留的 `DSH_PODMAN` 命名空间:agent
75
+ 自身的 token 保留在 agent 中,而不会被复制到它所启动的每个东西中。以
76
+ `inheritEnv=false` 启动的守护进程则只接收 `PATH`/`HOME` 基线以及自身的 `env`。
77
+
78
+ 每个容器还携带有序的 **PATH 附加项**。orchestrator
79
+ 会持久化它们(`SetContainerPaths`),并在每次创建或重建时以
80
+ `DSH_PODMAN_GUEST_PATHS` 交给 guest agent;agent 将它们保存在内存中(通过其 gRPC
81
+ API 的 `SetPaths`/`GetPaths`),并添加到它启动的每个进程的 PATH 之前,包括插件的
82
+ `ctx.subprocess` 提供者,因此内置的 shell
83
+ 和文件系统工具也能看到它们。更改列表不会重建容器,因此先前启动的守护进程仍使用旧的
84
+ PATH。
85
+
86
+ 在 `read-only` 权限下,harness 沙箱无法约束命令(插件替换了
87
+ `ctx.subprocess`/`ctx.fs` 并解开了 `landlock-run`
88
+ 包装),因此由插件自行执行该模式:只有当所有带模式的挂载都是 `read_only`
89
+ 时,shell 或文件工具才能运行。否则插件会以保留的工具名
90
+ `dsh_podman_builtin_remount_read_only` 通过审批服务询问——浏览器端会用其自己的
91
+ **重新挂载并运行**
92
+ 按钮渲染该提示——并在获准后以强制只读的挂载重建容器,再运行该工具。出于同样的原因,harness
93
+ 的 `sandbox_permissions` 升权会被直接授予而不询问:它请求的放宽在此处没有效果。
94
+
95
+ 重新创建容器或关闭 orchestrator 时,会先要求容器的 guest agent
96
+ 优雅地停止其守护进程(SIGTERM,约 10 秒宽限期),然后 podman 才会拆除该容器。
97
+
98
+ ## 镜像
99
+
100
+ orchestrator 通过 Podman 构建工作区镜像(有关原始/基础/自定义分层,请参阅
101
+ [使用](usage.zh.md#镜像模型))。基础镜像在其原始引用的基础上在本地构建(或在
102
+ `DSH_PODMAN_BASE_IMAGE_PREFIX` 指向注册表时拉取),不包含任何 guest-agent
103
+ 二进制。guest agent 在创建容器时通过将其自身镜像只读挂载到容器中来提供(请参阅
104
+ [配置](configuration.zh.md))。自定义镜像在父镜像之上分层并添加额外的软件包。
105
+ 每次构建都会以包含所生成 Containerfile 的 tar 形式将构建上下文流式传输给 Podman
106
+ API,因此构建不会向 orchestrator 的状态目录写入任何内容。
107
+
108
+ `image_rebuild_all` 首先确保每个基础镜像(根据 `DSH_PODMAN_BASE_IMAGE_PREFIX`
109
+ 在本地构建或从公共注册表拉取),然后**按依赖顺序**重建存储的自定义镜像——每个父镜像都在从它派生的镜像之前。重建失败的镜像以及依赖它的每个镜像都会被报告在
110
+ `skipped` 中,其余镜像继续。
111
+
112
+ 配置了主机缓存时(`DSH_PODMAN_HOST_*_CACHE`,同时挂载到构建容器和 orchestrator
113
+ 中),构建会复用已下载的软件包。设置卡片会报告每个缓存的大小并可清理它——保留每个软件包的最新版本,或清空缓存。构建器用读写锁将清理与构建串行化,因此清理绝不会在构建进行时删除其正在使用的软件包。
114
+
115
+ ## 设置卡片传输
116
+
117
+ 卡片通过 harness API 路径(`/api/podman/card`)之下的一个已认证 fetch
118
+ 路由与主机通信;该路由注册在 connection 服务上,因此承载层会先应用其 Host/Origin
119
+ 校验和浏览器认证:
120
+
121
+ - `GET` 返回 orchestrator
122
+ 的实时快照(容器、镜像、工作区、卷、机密、缓存),每次请求都重新构建。
123
+ - `POST` 针对 orchestrator 恰好执行一个命令(`remove` / `recreate` / `create` /
124
+ `image_rebuild` / `image_rebuild_all` / volume / secret / secret-env / mount
125
+ 操作),并返回要显示的提示。
126
+
127
+ 因此设置命名空间中**只保留真正的偏好**:默认镜像、sockets
128
+ 根目录,以及卡片的当前语言(`uiLocale`,以便主机以会话语言呈现审批文本——参见[审批](usage.zh.md#审批))。orchestrator
129
+ 派生的任何内容都不会被持久化,也不会有命令经由设置文档往返。
@@ -0,0 +1,160 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
3
+
4
+ SPDX-License-Identifier: MIT
5
+ -->
6
+
7
+ # Configuration
8
+
9
+ Environment variables use the `DSH_PODMAN_` prefix and are listed under the
10
+ component that reads them (a variable read by several components appears in each
11
+ of their sections). The plugin also exposes a few **UI settings** in the dsh
12
+ **Settings → Plugins** card, listed separately from env vars.
13
+
14
+ The plugin reads its configuration from, in order: the plugin `config` in
15
+ cordis, then the environment variables below, then built-in defaults.
16
+
17
+ ## Plugin (dsh client) — environment variables
18
+
19
+ | Variable | Default | Description |
20
+ | ------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------ |
21
+ | `DSH_PODMAN_IMAGE_PREFIX` | `localhost/dsh-podman/` | Prefix prepended to workspace image references |
22
+ | `DSH_PODMAN_ORCHESTRATOR_TOKEN` | — | Shared secret authenticating control-plane gRPC calls; see [Variable details](#variable-details) |
23
+ | `DSH_PODMAN_PROJECTS_ROOT` | `/projects` | Project root used to resolve session working directories into a workspace |
24
+ | `DSH_PODMAN_SOCKETS_ROOT` | `/run/dsh-podman` | Socket root the plugin derives the orchestrator control socket (`orchestrator.sock`) from |
25
+
26
+ `projectsRoot` and `imagePrefix` are env-only so they match the orchestrator;
27
+ `controlToken` comes from the plugin `config` or
28
+ `DSH_PODMAN_ORCHESTRATOR_TOKEN`.
29
+
30
+ ## Plugin (dsh client) — UI settings
31
+
32
+ Editable in the card's **Settings → Plugins → Podman** panel (the Configuration
33
+ section and the images' Set-default popup):
34
+
35
+ | Setting | Default | Description |
36
+ | -------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------- |
37
+ | `defaultImage` | `archlinux` | Image **short name** used for new workspaces; chosen from base and custom images via the Set-default popup |
38
+ | `socketsRoot` | `DSH_PODMAN_SOCKETS_ROOT` | Socket root the plugin uses to reach the orchestrator; falls back to the env var |
39
+
40
+ | Variable | Default | Description |
41
+ | ---------------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
42
+ | `DSH_PODMAN_BASE_IMAGE_PREFIX` | `localhost/dsh-podman/base/` | Prefix under which base images are tagged; a `localhost/` prefix builds them locally, otherwise they are pulled from a public registry |
43
+ | `DSH_PODMAN_GUEST_AGENT_IMAGE` | — | Guest-agent image mounted read-only into every guest container; unset disables the feature; see [Variable details](#variable-details) |
44
+ | `DSH_PODMAN_GUEST_AGENT_IMAGE_AGENT_BIN` | `/bin/dsh-podman-guest-agent` | Path of the guest agent binary inside the guest-agent image; the container command is `<mount>/<agent_bin>`; see [Variable details](#variable-details) |
45
+ | `DSH_PODMAN_GUEST_AGENT_IMAGE_MOUNT` | `/opt/dsh-podman/guest-agent` | Container-internal directory where the guest-agent image is mounted read-only; see [Variable details](#variable-details) |
46
+ | `DSH_PODMAN_GUEST_AGENT_IMAGE_USE_VERSION_TAG` | `false` | When truthy, the guest-agent image reference uses the orchestrator's git version as its tag (replacing the tag in `DSH_PODMAN_GUEST_AGENT_IMAGE`, or adding one when absent); a digest reference panics at startup |
47
+ | `DSH_PODMAN_HOST_APK_CACHE` | — | Host-absolute directory mounted at `/etc/apk/cache` to persist downloaded packages across apk builds; unset disables caching |
48
+ | `DSH_PODMAN_HOST_APT_CACHE` | — | Host-absolute directory mounted at `/var/cache/apt/archives` to persist downloaded packages across apt builds; unset disables caching |
49
+ | `DSH_PODMAN_HOST_GUEST_AGENT_BIN` | — | Host-side guest agent binary path; bind-mounted when set; see [Variable details](#variable-details) |
50
+ | `DSH_PODMAN_HOST_PACMAN_CACHE` | — | Host-absolute directory mounted at `/var/cache/pacman/pkg` to persist downloaded packages across pacman builds; unset disables caching |
51
+ | `DSH_PODMAN_HOST_PROJECTS_ROOT` | `DSH_PODMAN_PROJECTS_ROOT` | Host-side projects root used as the source of bind mounts |
52
+ | `DSH_PODMAN_HOST_SOCKETS_ROOT` | `DSH_PODMAN_SOCKETS_ROOT` | Host-side sockets root for guest socket bind mounts |
53
+ | `DSH_PODMAN_IMAGE_PREFIX` | `localhost/dsh-podman/` | Prefix prepended to built workspace image references |
54
+ | `DSH_PODMAN_ORCHESTRATOR_PODMAN_SOCKET` | required | Podman API socket, e.g. `unix:///run/podman/podman.sock` |
55
+ | `DSH_PODMAN_ORCHESTRATOR_STATE` | `/var/lib/dsh-orchestrator` | Persisted state directory |
56
+ | `DSH_PODMAN_ORCHESTRATOR_TOKEN` | — | Shared secret authenticating control-plane gRPC calls; see [Variable details](#variable-details) |
57
+ | `DSH_PODMAN_PROJECTS_ROOT` | `/projects` | Project root inside every guest container |
58
+ | `DSH_PODMAN_SECRET_PREFIX` | `dsh-podman-` | Prefix applied to managed podman secrets (see [Usage](usage.md#secrets)) |
59
+ | `DSH_PODMAN_SOCKETS_ROOT` | `/run/dsh-podman` | Socket root directory (bind-mounted from the host); holds `orchestrator.sock` and per-workspace guest sockets; see [Variable details](#variable-details) |
60
+ | `DSH_PODMAN_VOLUME_PREFIX` | `dsh-podman-` | Prefix applied to managed named volumes (see [Usage](usage.md#mounts-and-volumes)) |
61
+
62
+ ## Guest agent (`dsh-podman-guest-agent`)
63
+
64
+ | Variable | Default | Description |
65
+ | -------------------------- | ----------- | ------------------------------------------------------------------------------------------- |
66
+ | `DSH_PODMAN_GUEST_SOCKET` | required | Unix socket the guest agent serves on; the orchestrator sets it when starting the container |
67
+ | `DSH_PODMAN_GUEST_TOKEN` | — | Bearer token required on every gRPC call; see [Variable details](#variable-details) |
68
+ | `DSH_PODMAN_PROJECTS_ROOT` | `/projects` | Root where the workspace's project(s) are mounted |
69
+
70
+ ## Variable details
71
+
72
+ ### `DSH_PODMAN_BASE_IMAGE_PREFIX`
73
+
74
+ Prefix under which base images are tagged, as
75
+ `BASE_IMAGE_PREFIX + <short> +
76
+ ":latest"`. When the prefix starts with
77
+ `localhost/`, base images are **built locally** by the orchestrator from their
78
+ upstream primitive reference; any other prefix marks them as **public**, in
79
+ which case the orchestrator **pulls** the tagged base images from that registry
80
+ instead of building them. The built-in base images are `archlinux` (pacman),
81
+ `ubuntu` (apt), and `alpine` (apk); their short names are reserved and cannot be
82
+ built over, rebuilt, or removed as custom images.
83
+
84
+ ### `DSH_PODMAN_GUEST_AGENT_IMAGE` and `DSH_PODMAN_HOST_GUEST_AGENT_BIN`
85
+
86
+ When `DSH_PODMAN_GUEST_AGENT_IMAGE` is set, the orchestrator mounts that image
87
+ read-only into every guest container at `DSH_PODMAN_GUEST_AGENT_IMAGE_MOUNT`
88
+ (default `/opt/dsh-podman/guest-agent`) and runs the guest agent binary from
89
+ `<mount>/<agent_bin>` — that path is the container command (see the next section
90
+ for the binary path and mount location).
91
+
92
+ `DSH_PODMAN_HOST_GUEST_AGENT_BIN` is the path _on the host_ of a guest agent
93
+ binary. Setting it bind-mounts that binary read-only to the same in-container
94
+ path instead of mounting the guest-agent image; it is an optional development
95
+ fallback and takes precedence over the image mount. It is unset by default.
96
+
97
+ If neither variable is configured, the guest container has no guest agent to
98
+ run, and creating a workspace container fails.
99
+
100
+ ### `DSH_PODMAN_GUEST_AGENT_IMAGE`, `DSH_PODMAN_GUEST_AGENT_IMAGE_AGENT_BIN`, `DSH_PODMAN_GUEST_AGENT_IMAGE_MOUNT` and `DSH_PODMAN_GUEST_AGENT_IMAGE_USE_VERSION_TAG`
101
+
102
+ The guest-agent image provides the agent at container creation: the orchestrator
103
+ mounts it **read-only** into every guest container at
104
+ `DSH_PODMAN_GUEST_AGENT_IMAGE_MOUNT` (default `/opt/dsh-podman/guest-agent`)
105
+ using podman's image-mount mechanism, and the container command is
106
+ `<mount>/<agent_bin>`. Podman image volumes are always mounted read-only.
107
+
108
+ The mount is **hidden** — the orchestrator injects it itself, so it is not
109
+ listed among the user mounts. Because the agent is provided at runtime,
110
+ rebuilding base images is no longer needed when the guest-agent version changes.
111
+
112
+ `DSH_PODMAN_GUEST_AGENT_IMAGE_AGENT_BIN` is the path of the binary inside the
113
+ guest-agent image (default `/bin/dsh-podman-guest-agent`).
114
+
115
+ With `DSH_PODMAN_GUEST_AGENT_IMAGE_USE_VERSION_TAG` set to a truthy value, the
116
+ orchestrator uses its own git version as the image tag instead of the one in
117
+ `DSH_PODMAN_GUEST_AGENT_IMAGE` (which may omit the tag entirely). Because a
118
+ digest reference cannot be overridden, the orchestrator fails to start when that
119
+ variable is set alongside a digest-style `DSH_PODMAN_GUEST_AGENT_IMAGE`.
120
+
121
+ ### `DSH_PODMAN_GUEST_TOKEN`
122
+
123
+ Shared secret required on every guest-agent gRPC call, carried as the gRPC
124
+ metadata header `authorization: bearer <token>`. In practice the orchestrator
125
+ generates a fresh random token for each workspace and injects it into the guest
126
+ container (via `DSH_PODMAN_GUEST_TOKEN`), handing the same value to the plugin
127
+ together with the guest socket path — so it normally needs no manual
128
+ configuration.
129
+
130
+ ### `DSH_PODMAN_ORCHESTRATOR_TOKEN`
131
+
132
+ An arbitrary shared secret string that the orchestrator and the plugin must
133
+ agree on; every control-plane request carries it as the gRPC metadata header
134
+ `authorization: bearer <token>`. Use a long, random value — for example
135
+ `openssl rand -hex 32` — and set the same value on both sides. When the
136
+ orchestrator has no token set, it accepts unauthenticated control-plane calls
137
+ (relying on the socket's file permissions instead); when a token is set,
138
+ requests without the matching header are rejected with `Unauthenticated`.
139
+
140
+ ### `DSH_PODMAN_SOCKETS_ROOT` and `DSH_PODMAN_HOST_SOCKETS_ROOT`
141
+
142
+ `DSH_PODMAN_SOCKETS_ROOT` is the socket root directory shared by the
143
+ orchestrator and the guest containers. It holds the orchestrator control socket
144
+ (`orchestrator.sock`) and one subdirectory per workspace, where each guest agent
145
+ creates its `guest.sock`.
146
+
147
+ The directory needs to be bind-mounted in the orchestrator container. Its mode
148
+ must be `0700`: the orchestrator refuses to start when the socket root is group-
149
+ or world-accessible, since the control plane is a full-privilege interface onto
150
+ the Podman API and the socket's own mode only helps while the directory above it
151
+ stays private.
152
+
153
+ Each guest container gets exactly one socket directory bind-mounted into it: the
154
+ host directory `<DSH_PODMAN_HOST_SOCKETS_ROOT>/<container>` is mounted at
155
+ `<DSH_PODMAN_SOCKETS_ROOT>/<container>` inside the container. Because only that
156
+ single per-workspace directory is mounted, a guest container never sees the
157
+ orchestrator's `orchestrator.sock` nor any other workspace's socket directory.
158
+ Both the control socket and each guest socket are created with mode `0600`, and
159
+ both processes set a `0077` umask at startup so the socket is never briefly
160
+ reachable between `bind` and `chmod`.
@@ -0,0 +1,147 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
3
+
4
+ SPDX-License-Identifier: MIT
5
+ -->
6
+
7
+ # 配置
8
+
9
+ 环境变量使用 `DSH_PODMAN_`
10
+ 前缀,并按读取它们的组件分组列出(被多个组件读取的变量会出现在每个组件的对应章节中)。插件还在
11
+ dsh 的 **设置 → 插件** 卡片中暴露了一些 **UI 设置**,与环境变量分开列出。
12
+
13
+ 插件按以下顺序读取其配置:先是 cordis 中的插件
14
+ `config`,然后是下方列出的环境变量,最后是内置默认值。
15
+
16
+ ## 插件(dsh 客户端)— 环境变量
17
+
18
+ | 变量 | 默认值 | 说明 |
19
+ | ------------------------------- | ----------------------- | ------------------------------------------------------------------------- |
20
+ | `DSH_PODMAN_IMAGE_PREFIX` | `localhost/dsh-podman/` | 前置到工作区镜像引用上的前缀 |
21
+ | `DSH_PODMAN_ORCHESTRATOR_TOKEN` | — | 用于认证控制平面 gRPC 调用的共享机密;见[变量详解](#变量详解) |
22
+ | `DSH_PODMAN_PROJECTS_ROOT` | `/projects` | 用于将会话工作目录解析为工作区的项目根目录 |
23
+ | `DSH_PODMAN_SOCKETS_ROOT` | `/run/dsh-podman` | 插件据此推导 orchestrator 控制套接字(`orchestrator.sock`)的套接字根目录 |
24
+
25
+ `projectsRoot` 和 `imagePrefix` 仅来自环境变量,以确保与 orchestrator
26
+ 一致;`controlToken` 来自插件 `config` 或 `DSH_PODMAN_ORCHESTRATOR_TOKEN`。
27
+
28
+ ## 插件(dsh 客户端)— UI 设置
29
+
30
+ 可在卡片中的 **设置 → 插件 → Podman** 面板内编辑(配置部分和镜像的 Set-default
31
+ 弹窗):
32
+
33
+ | 设置 | 默认值 | 说明 |
34
+ | -------------- | ------------------------- | --------------------------------------------------------------------------------- |
35
+ | `defaultImage` | `archlinux` | 用于新工作区的镜像**短名称**;可通过 Set-default 弹窗从基础镜像和自定义镜像中选择 |
36
+ | `socketsRoot` | `DSH_PODMAN_SOCKETS_ROOT` | 插件用于连接 orchestrator 的套接字根目录;回退到环境变量 |
37
+
38
+ | 变量 | 默认值 | 说明 |
39
+ | ---------------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
40
+ | `DSH_PODMAN_BASE_IMAGE_PREFIX` | `localhost/dsh-podman/base/` | 基础镜像的标签前缀;以 `localhost/` 开头的前缀会在本地构建它们,否则从公共镜像仓库拉取 |
41
+ | `DSH_PODMAN_GUEST_AGENT_IMAGE` | — | 以只读方式挂载到每个 guest 容器中的 guest-agent 镜像;未设置时禁用该功能;见[变量详解](#变量详解) |
42
+ | `DSH_PODMAN_GUEST_AGENT_IMAGE_AGENT_BIN` | `/bin/dsh-podman-guest-agent` | guest-agent 镜像中 guest agent 二进制的路径;容器命令为 `<mount>/<agent_bin>`;见[变量详解](#变量详解) |
43
+ | `DSH_PODMAN_GUEST_AGENT_IMAGE_MOUNT` | `/opt/dsh-podman/guest-agent` | guest-agent 镜像以只读方式挂载到的容器内部目录;见[变量详解](#变量详解) |
44
+ | `DSH_PODMAN_GUEST_AGENT_IMAGE_USE_VERSION_TAG` | `false` | 为真值时,guest-agent 镜像引用使用 orchestrator 的 git 版本作为其标签(替换 `DSH_PODMAN_GUEST_AGENT_IMAGE` 中的标签,或在缺失时添加一个);摘要引用会在启动时 panic |
45
+ | `DSH_PODMAN_HOST_APK_CACHE` | — | 挂载在 `/etc/apk/cache` 的主机绝对路径目录,用于在 apk 构建之间持久化已下载的软件包;未设置时禁用缓存 |
46
+ | `DSH_PODMAN_HOST_APT_CACHE` | — | 挂载在 `/var/cache/apt/archives` 的主机绝对路径目录,用于在 apt 构建之间持久化已下载的软件包;未设置时禁用缓存 |
47
+ | `DSH_PODMAN_HOST_GUEST_AGENT_BIN` | — | 主机侧的 guest agent 二进制路径;设置后会绑定挂载;见[变量详解](#变量详解) |
48
+ | `DSH_PODMAN_HOST_PACMAN_CACHE` | — | 挂载在 `/var/cache/pacman/pkg` 的主机绝对路径目录,用于在 pacman 构建之间持久化已下载的软件包;未设置时禁用缓存 |
49
+ | `DSH_PODMAN_HOST_PROJECTS_ROOT` | `DSH_PODMAN_PROJECTS_ROOT` | 用作绑定挂载源的主机侧项目根目录 |
50
+ | `DSH_PODMAN_HOST_SOCKETS_ROOT` | `DSH_PODMAN_SOCKETS_ROOT` | 用于 guest 套接字绑定挂载的主机侧套接字根目录 |
51
+ | `DSH_PODMAN_IMAGE_PREFIX` | `localhost/dsh-podman/` | 前置到已构建的工作区镜像引用上的前缀 |
52
+ | `DSH_PODMAN_ORCHESTRATOR_PODMAN_SOCKET` | required | Podman API 套接字,例如 `unix:///run/podman/podman.sock` |
53
+ | `DSH_PODMAN_ORCHESTRATOR_STATE` | `/var/lib/dsh-orchestrator` | 持久化状态目录 |
54
+ | `DSH_PODMAN_ORCHESTRATOR_TOKEN` | — | 用于认证控制平面 gRPC 调用的共享机密;见[变量详解](#变量详解) |
55
+ | `DSH_PODMAN_PROJECTS_ROOT` | `/projects` | 每个 guest 容器内的项目根目录 |
56
+ | `DSH_PODMAN_SECRET_PREFIX` | `dsh-podman-` | 应用于受管 podman 机密的前缀(见[使用](usage.zh.md#机密)) |
57
+ | `DSH_PODMAN_SOCKETS_ROOT` | `/run/dsh-podman` | 套接字根目录(从主机绑定挂载);包含 `orchestrator.sock` 和每个工作区的 guest 套接字;见[变量详解](#变量详解) |
58
+ | `DSH_PODMAN_VOLUME_PREFIX` | `dsh-podman-` | 应用于受管命名卷的前缀(见[使用](usage.zh.md#挂载与卷)) |
59
+
60
+ ## Guest agent(`dsh-podman-guest-agent`)
61
+
62
+ | 变量 | 默认值 | 说明 |
63
+ | -------------------------- | ----------- | ------------------------------------------------------------------- |
64
+ | `DSH_PODMAN_GUEST_SOCKET` | required | guest agent 提供服务的 Unix 套接字;orchestrator 在启动容器时设置它 |
65
+ | `DSH_PODMAN_GUEST_TOKEN` | — | 每次 gRPC 调用所需的 Bearer 令牌;见[变量详解](#变量详解) |
66
+ | `DSH_PODMAN_PROJECTS_ROOT` | `/projects` | 工作区的项目被挂载到的根目录 |
67
+
68
+ ## 变量详解
69
+
70
+ ### `DSH_PODMAN_BASE_IMAGE_PREFIX`
71
+
72
+ 基础镜像的标签前缀,格式为 `BASE_IMAGE_PREFIX + <short> + ":latest"`。当前缀以
73
+ `localhost/` 开头时,orchestrator
74
+ 会从其上游原始引用**本地构建**基础镜像;任何其他前缀则将其标记为**公共**镜像,此时
75
+ orchestrator
76
+ 会从该镜像仓库**拉取**带标签的基础镜像,而不是构建它们。内置基础镜像为
77
+ `archlinux`(pacman)、`ubuntu`(apt)和
78
+ `alpine`(apk);它们的短名称是保留的,不能作为自定义镜像覆盖、重建或删除。
79
+
80
+ ### `DSH_PODMAN_GUEST_AGENT_IMAGE` and `DSH_PODMAN_HOST_GUEST_AGENT_BIN`
81
+
82
+ 当设置了 `DSH_PODMAN_GUEST_AGENT_IMAGE` 时,orchestrator
83
+ 会将该镜像以只读方式挂载到每个 guest 容器的
84
+ `DSH_PODMAN_GUEST_AGENT_IMAGE_MOUNT`(默认 `/opt/dsh-podman/guest-agent`),并从
85
+ `<mount>/<agent_bin>` 运行 guest agent
86
+ 二进制——该路径就是容器命令(二进制路径和挂载位置见下一节)。
87
+
88
+ `DSH_PODMAN_HOST_GUEST_AGENT_BIN` 是 guest agent
89
+ 二进制在_主机上_的路径。设置它会将该二进制以只读方式绑定挂载到容器内相同路径,而不是挂载
90
+ guest-agent 镜像;这是一个可选的开发回退方案,优先于镜像挂载。默认情况下未设置。
91
+
92
+ 如果两个变量都未配置,guest 容器将没有可运行的 guest
93
+ agent,创建工作区容器将会失败。
94
+
95
+ ### `DSH_PODMAN_GUEST_AGENT_IMAGE`, `DSH_PODMAN_GUEST_AGENT_IMAGE_AGENT_BIN`, `DSH_PODMAN_GUEST_AGENT_IMAGE_MOUNT` and `DSH_PODMAN_GUEST_AGENT_IMAGE_USE_VERSION_TAG`
96
+
97
+ guest-agent 镜像在容器创建时提供 agent:orchestrator 使用 podman
98
+ 的镜像挂载机制,将其**只读**挂载到每个 guest 容器的
99
+ `DSH_PODMAN_GUEST_AGENT_IMAGE_MOUNT`(默认
100
+ `/opt/dsh-podman/guest-agent`),容器命令为 `<mount>/<agent_bin>`。Podman
101
+ 镜像卷始终以只读方式挂载。
102
+
103
+ 该挂载是**隐藏的**——由 orchestrator 自行注入,因此不会列在用户挂载中。由于 agent
104
+ 是在运行时提供的,guest-agent 版本变化时不再需要重建基础镜像。
105
+
106
+ `DSH_PODMAN_GUEST_AGENT_IMAGE_AGENT_BIN` 是 guest-agent 镜像内二进制的路径(默认
107
+ `/bin/dsh-podman-guest-agent`)。
108
+
109
+ 当 `DSH_PODMAN_GUEST_AGENT_IMAGE_USE_VERSION_TAG` 设置为真值时,orchestrator
110
+ 使用自己的 git 版本作为镜像标签,而不是 `DSH_PODMAN_GUEST_AGENT_IMAGE`
111
+ 中的标签(该标签可以完全省略)。由于摘要引用无法被覆盖,当该变量与摘要式的
112
+ `DSH_PODMAN_GUEST_AGENT_IMAGE` 一起设置时,orchestrator 将无法启动。
113
+
114
+ ### `DSH_PODMAN_GUEST_TOKEN`
115
+
116
+ 每次 guest-agent gRPC 调用所需的共享机密,以 gRPC 元数据头
117
+ `authorization: bearer <token>` 传递。实际上,orchestrator
118
+ 会为每个工作区生成一个新的随机令牌,并通过 `DSH_PODMAN_GUEST_TOKEN` 注入到 guest
119
+ 容器中,同时将相同的值连同 guest 套接字路径一起交给插件——因此通常无需手动配置。
120
+
121
+ ### `DSH_PODMAN_ORCHESTRATOR_TOKEN`
122
+
123
+ orchestrator 和插件必须约定的任意共享机密字符串;每个控制平面请求都以 gRPC
124
+ 元数据头 `authorization: bearer <token>` 携带它。请使用一个长且随机的值——例如
125
+ `openssl rand -hex 32`——并在两端设置相同的值。当 orchestrator
126
+ 未设置令牌时,它接受未经认证的控制平面调用(转而依赖套接字的文件权限);当设置了令牌时,不带匹配头的请求会被以
127
+ `Unauthenticated` 拒绝。
128
+
129
+ ### `DSH_PODMAN_SOCKETS_ROOT` and `DSH_PODMAN_HOST_SOCKETS_ROOT`
130
+
131
+ `DSH_PODMAN_SOCKETS_ROOT` 是 orchestrator 与 guest
132
+ 容器共享的套接字根目录。它包含 orchestrator
133
+ 控制套接字(`orchestrator.sock`)和每个工作区的一个子目录,每个 guest agent
134
+ 在其中创建自己的 `guest.sock`。
135
+
136
+ 该目录需要绑定挂载到 orchestrator 容器中。其权限模式必须是
137
+ `0700`:当套接字根目录对组或其他用户可访问时,orchestrator
138
+ 拒绝启动,因为控制平面是通往 Podman API
139
+ 的全权限接口,且只有在其上层目录保持私有时,套接字自身的权限模式才有作用。
140
+
141
+ 每个 guest 容器恰好绑定挂载一个套接字目录:主机目录
142
+ `<DSH_PODMAN_HOST_SOCKETS_ROOT>/<container>` 被挂载到容器内的
143
+ `<DSH_PODMAN_SOCKETS_ROOT>/<container>`。由于只挂载了这一个按工作区划分的目录,guest
144
+ 容器永远看不到 orchestrator 的
145
+ `orchestrator.sock`,也看不到任何其他工作区的套接字目录。控制套接字和每个 guest
146
+ 套接字都以权限模式 `0600` 创建,两个进程都会在启动时设置 `0077`
147
+ umask,这样套接字在 `bind` 和 `chmod` 之间永远不会被短暂访问。