jishushell 0.4.30 → 0.5.15

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 (182) hide show
  1. package/apps/anythingllm-container.yaml +287 -0
  2. package/apps/browserless-chromium-container.yaml +18 -6
  3. package/apps/filebrowser-container.yaml +163 -0
  4. package/apps/openclaw-binary.yaml +8 -0
  5. package/apps/openclaw-container.yaml +9 -1
  6. package/apps/openclaw-with-searxng-container.yaml +4 -0
  7. package/apps/searxng-container.yaml +5 -4
  8. package/apps/weknora-container.yaml +471 -0
  9. package/dist/cli/panel.js.map +1 -1
  10. package/dist/config.d.ts +19 -0
  11. package/dist/config.js +99 -1
  12. package/dist/config.js.map +1 -1
  13. package/dist/install.js +3 -3
  14. package/dist/install.js.map +1 -1
  15. package/dist/routes/auth.js +2 -2
  16. package/dist/routes/auth.js.map +1 -1
  17. package/dist/routes/backup.js +64 -11
  18. package/dist/routes/backup.js.map +1 -1
  19. package/dist/routes/external-mounts.d.ts +17 -0
  20. package/dist/routes/external-mounts.js +73 -0
  21. package/dist/routes/external-mounts.js.map +1 -0
  22. package/dist/routes/file-mounts.d.ts +13 -0
  23. package/dist/routes/file-mounts.js +90 -0
  24. package/dist/routes/file-mounts.js.map +1 -0
  25. package/dist/routes/files-organize.d.ts +28 -0
  26. package/dist/routes/files-organize.js +167 -0
  27. package/dist/routes/files-organize.js.map +1 -0
  28. package/dist/routes/files.d.ts +31 -0
  29. package/dist/routes/files.js +321 -0
  30. package/dist/routes/files.js.map +1 -0
  31. package/dist/routes/instances.js +45 -7
  32. package/dist/routes/instances.js.map +1 -1
  33. package/dist/routes/internal.d.ts +2 -0
  34. package/dist/routes/internal.js +59 -0
  35. package/dist/routes/internal.js.map +1 -0
  36. package/dist/routes/setup.js +9 -9
  37. package/dist/routes/setup.js.map +1 -1
  38. package/dist/routes/system.js +1 -1
  39. package/dist/routes/system.js.map +1 -1
  40. package/dist/routes/webdav.d.ts +17 -0
  41. package/dist/routes/webdav.js +114 -0
  42. package/dist/routes/webdav.js.map +1 -0
  43. package/dist/server.js +341 -3
  44. package/dist/server.js.map +1 -1
  45. package/dist/services/app/app-compiler.d.ts +1 -1
  46. package/dist/services/app/app-compiler.js +5 -5
  47. package/dist/services/app/app-compiler.js.map +1 -1
  48. package/dist/services/app/app-manager.d.ts +1 -0
  49. package/dist/services/app/app-manager.js +172 -41
  50. package/dist/services/app/app-manager.js.map +1 -1
  51. package/dist/services/app/custom-manager.js.map +1 -1
  52. package/dist/services/app/hermes-agent-manager.js +1 -0
  53. package/dist/services/app/hermes-agent-manager.js.map +1 -1
  54. package/dist/services/app/ollama-manager.js +1 -1
  55. package/dist/services/app/ollama-manager.js.map +1 -1
  56. package/dist/services/app/openclaw-manager.js +20 -3
  57. package/dist/services/app/openclaw-manager.js.map +1 -1
  58. package/dist/services/app/platform-transform.d.ts +32 -0
  59. package/dist/services/app/platform-transform.js +65 -0
  60. package/dist/services/app/platform-transform.js.map +1 -0
  61. package/dist/services/app-passwords.d.ts +61 -0
  62. package/dist/services/app-passwords.js +173 -0
  63. package/dist/services/app-passwords.js.map +1 -0
  64. package/dist/services/backup-manager.d.ts +11 -0
  65. package/dist/services/backup-manager.js +177 -4
  66. package/dist/services/backup-manager.js.map +1 -1
  67. package/dist/services/connection-apply.d.ts +2 -0
  68. package/dist/services/connection-apply.js +55 -1
  69. package/dist/services/connection-apply.js.map +1 -1
  70. package/dist/services/connection-resolver.js +1 -1
  71. package/dist/services/connection-resolver.js.map +1 -1
  72. package/dist/services/connection-transactor.d.ts +2 -0
  73. package/dist/services/connection-transactor.js +12 -2
  74. package/dist/services/connection-transactor.js.map +1 -1
  75. package/dist/services/external-mounts.d.ts +40 -0
  76. package/dist/services/external-mounts.js +187 -0
  77. package/dist/services/external-mounts.js.map +1 -0
  78. package/dist/services/files-manager.d.ts +252 -0
  79. package/dist/services/files-manager.js +1075 -0
  80. package/dist/services/files-manager.js.map +1 -0
  81. package/dist/services/files-mounts.d.ts +42 -0
  82. package/dist/services/files-mounts.js +207 -0
  83. package/dist/services/files-mounts.js.map +1 -0
  84. package/dist/services/instance-manager.js +1 -23
  85. package/dist/services/instance-manager.js.map +1 -1
  86. package/dist/services/llm-proxy/index.js.map +1 -1
  87. package/dist/services/llm-proxy/ssrf.js +6 -2
  88. package/dist/services/llm-proxy/ssrf.js.map +1 -1
  89. package/dist/services/nomad-manager.d.ts +4 -0
  90. package/dist/services/nomad-manager.js +53 -19
  91. package/dist/services/nomad-manager.js.map +1 -1
  92. package/dist/services/organize/applier.d.ts +46 -0
  93. package/dist/services/organize/applier.js +218 -0
  94. package/dist/services/organize/applier.js.map +1 -0
  95. package/dist/services/organize/rules.d.ts +57 -0
  96. package/dist/services/organize/rules.js +286 -0
  97. package/dist/services/organize/rules.js.map +1 -0
  98. package/dist/services/organize/scanner.d.ts +50 -0
  99. package/dist/services/organize/scanner.js +366 -0
  100. package/dist/services/organize/scanner.js.map +1 -0
  101. package/dist/services/organize/store.d.ts +14 -0
  102. package/dist/services/organize/store.js +82 -0
  103. package/dist/services/organize/store.js.map +1 -0
  104. package/dist/services/panel-manager.js +20 -1
  105. package/dist/services/panel-manager.js.map +1 -1
  106. package/dist/services/process-manager.js +3 -2
  107. package/dist/services/process-manager.js.map +1 -1
  108. package/dist/services/runtime/adapters/hermes.js +1 -1
  109. package/dist/services/runtime/adapters/hermes.js.map +1 -1
  110. package/dist/services/runtime/adapters/openclaw-routes.d.ts +8 -2
  111. package/dist/services/runtime/adapters/openclaw-routes.js +68 -0
  112. package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -1
  113. package/dist/services/runtime/adapters/openclaw.d.ts +90 -0
  114. package/dist/services/runtime/adapters/openclaw.js +957 -45
  115. package/dist/services/runtime/adapters/openclaw.js.map +1 -1
  116. package/dist/services/runtime/instance.d.ts +1 -1
  117. package/dist/services/runtime/instance.js +1 -1
  118. package/dist/services/runtime/instance.js.map +1 -1
  119. package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +46 -0
  120. package/dist/services/runtime/mcp-shims/anythingllm-shim.js +281 -0
  121. package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +1 -0
  122. package/dist/services/runtime/mcp-shims/drive-shim.d.ts +54 -0
  123. package/dist/services/runtime/mcp-shims/drive-shim.js +489 -0
  124. package/dist/services/runtime/mcp-shims/drive-shim.js.map +1 -0
  125. package/dist/services/runtime/types.d.ts +31 -0
  126. package/dist/services/setup-manager.js +93 -18
  127. package/dist/services/setup-manager.js.map +1 -1
  128. package/dist/services/suggestions.js.map +1 -1
  129. package/dist/services/webdav/server.d.ts +24 -0
  130. package/dist/services/webdav/server.js +420 -0
  131. package/dist/services/webdav/server.js.map +1 -0
  132. package/dist/services/webdav/xml-builder.d.ts +73 -0
  133. package/dist/services/webdav/xml-builder.js +156 -0
  134. package/dist/services/webdav/xml-builder.js.map +1 -0
  135. package/dist/services/workspace-builder.d.ts +29 -0
  136. package/dist/services/workspace-builder.js +188 -0
  137. package/dist/services/workspace-builder.js.map +1 -0
  138. package/dist/types.d.ts +60 -0
  139. package/dist/utils/path-locks.d.ts +30 -0
  140. package/dist/utils/path-locks.js +63 -0
  141. package/dist/utils/path-locks.js.map +1 -0
  142. package/dist/utils/path-safety.d.ts +41 -0
  143. package/dist/utils/path-safety.js +119 -0
  144. package/dist/utils/path-safety.js.map +1 -0
  145. package/dist/utils/safe-write.d.ts +24 -0
  146. package/dist/utils/safe-write.js +82 -0
  147. package/dist/utils/safe-write.js.map +1 -0
  148. package/package.json +16 -1
  149. package/public/assets/Dashboard-BdWPtroF.js +1 -0
  150. package/public/assets/{HermesChatPanel-_GHoklgo.js → HermesChatPanel-B_2HlVBQ.js} +1 -1
  151. package/public/assets/{HermesConfigForm-anDnwUp_.js → HermesConfigForm-DVlhg3WV.js} +2 -2
  152. package/public/assets/{InitPassword-ZU9_-hDr.js → InitPassword-D7glTExX.js} +1 -1
  153. package/public/assets/InstanceDetail-CxSy2cpe.js +92 -0
  154. package/public/assets/{Login-BItXqYAJ.js → Login-Cfr5c2sv.js} +1 -1
  155. package/public/assets/NewInstance-BIYDmJis.js +1 -0
  156. package/public/assets/{ProviderRecommendations-DFYj7Fb6.js → ProviderRecommendations-BuRnvRcI.js} +1 -1
  157. package/public/assets/{Settings-Bttc6QmM.js → Settings-Cc-tYBil.js} +1 -1
  158. package/public/assets/{Setup-Bsxx1zgj.js → Setup-lGZEk5jq.js} +1 -1
  159. package/public/assets/{WeixinLoginPanel-DPZpAKgO.js → WeixinLoginPanel-CoGqzxeV.js} +2 -2
  160. package/public/assets/index-87IJXG-w.css +1 -0
  161. package/public/assets/index-BZc5zH7u.js +19 -0
  162. package/public/assets/{registry-5s2UB6is.js → registry-BWnkJgZ1.js} +2 -2
  163. package/public/assets/{usePolling-Do5Erqm_.js → usePolling-CwwT9KrC.js} +1 -1
  164. package/public/assets/{vendor-i18n-ucpM0OR0.js → vendor-i18n-y9V7Sfuu.js} +1 -1
  165. package/public/assets/{vendor-react-Bk1hRGiY.js → vendor-react-BWrEVJVb.js} +6 -6
  166. package/public/index.html +4 -4
  167. package/scripts/check-app-spec.mjs +18 -4
  168. package/scripts/check-new-file-tests.mjs +230 -0
  169. package/scripts/check-quarantine-expiry.mjs +105 -0
  170. package/scripts/perf/README.md +49 -0
  171. package/scripts/perf/auth.js +99 -0
  172. package/scripts/perf/config.js +63 -0
  173. package/scripts/perf/instances.js +143 -0
  174. package/scripts/perf/proxy.js +96 -0
  175. package/scripts/smoke/files-w1.sh +142 -0
  176. package/scripts/smoke-backend.mjs +122 -0
  177. package/scripts/smoke-post-publish.mjs +346 -0
  178. package/public/assets/Dashboard-rkWp-CXd.js +0 -1
  179. package/public/assets/InstanceDetail-CN0FH1aw.js +0 -92
  180. package/public/assets/NewInstance-BousE6kY.js +0 -1
  181. package/public/assets/index-8xZy1z5k.css +0 -1
  182. package/public/assets/index-Dw3HhUYE.js +0 -19
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xml-builder.js","sourceRoot":"","sources":["../../../src/services/webdav/xml-builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { FileMount } from "./runtime/types.js";
2
+ export declare class WorkspaceBuilderError extends Error {
3
+ reason: string;
4
+ constructor(message: string, reason: string);
5
+ }
6
+ export interface RebuildWorkspaceArgs {
7
+ /** Absolute host path of the instance's openclaw-home directory. */
8
+ openclawHome: string;
9
+ /** Absolute host path of FILES_ROOT (~/.jishushell/files). */
10
+ filesRoot: string;
11
+ /** Validated mount set; pass through validateMounts before calling. */
12
+ mounts: FileMount[];
13
+ /** Owning instance — used by isolation rule. */
14
+ instanceId: string;
15
+ }
16
+ export interface RebuildResult {
17
+ /** What kind of workspace we produced. */
18
+ layout: "named" | "root" | "empty";
19
+ /** For "named", the alias → resolved abs target map for audit. */
20
+ symlinks: Record<string, string>;
21
+ }
22
+ export declare function rebuildWorkspace(args: RebuildWorkspaceArgs): RebuildResult;
23
+ /**
24
+ * For audit / UI. Given the live workspace, report the layout in
25
+ * structural form without touching it.
26
+ */
27
+ export declare function describeWorkspace(openclawHome: string): RebuildResult & {
28
+ exists: boolean;
29
+ };
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Build the workspace symlink layout for an instance (M1 W2 PR-2).
3
+ *
4
+ * Given a validated FileMount[] and the host paths involved, this module
5
+ * (re)creates `${openclawHome}/workspace/` so that:
6
+ *
7
+ * - For named mounts: workspace is a real directory whose entries
8
+ * are alias symlinks → FILES_ROOT/{mount.path}
9
+ *
10
+ * - For a single root mount: workspace itself is a symlink →
11
+ * FILES_ROOT/{mount.path}, replacing the directory wholesale
12
+ *
13
+ * Why we don't touch `${openclawHome}/.openclaw/workspace/`:
14
+ * That subtree is OpenClaw's private state directory (skills config,
15
+ * sessions, mcporter.json). We give the user-facing mount surface a
16
+ * separate top-level directory `workspace/` directly under HOME,
17
+ * keeping the two namespaces strictly disjoint.
18
+ *
19
+ * Safety contract — refuses to clobber unknown content:
20
+ * - If workspace/ already contains plain files or non-symlink dirs
21
+ * (i.e. data we don't recognize), throw rather than touch.
22
+ * - Existing symlinks ARE removed — they were placed by a prior
23
+ * rebuildWorkspace call.
24
+ * - This means a *one-time legacy migration* (PR-7) must run first
25
+ * for instances whose workspace currently contains real data.
26
+ */
27
+ import * as fs from "node:fs";
28
+ import * as path from "node:path";
29
+ import { validateMounts, effectiveAlias, ensureMountTargets, } from "./files-mounts.js";
30
+ export class WorkspaceBuilderError extends Error {
31
+ reason;
32
+ constructor(message, reason) {
33
+ super(message);
34
+ this.reason = reason;
35
+ this.name = "WorkspaceBuilderError";
36
+ }
37
+ }
38
+ const WORKSPACE_DIR_NAME = "workspace";
39
+ export function rebuildWorkspace(args) {
40
+ const { openclawHome, filesRoot, mounts, instanceId } = args;
41
+ // Validate up front — caller may have skipped this; we are the last
42
+ // line of defense before fs writes.
43
+ const v = validateMounts(mounts, instanceId);
44
+ if (!v.ok) {
45
+ const summary = v.errors.slice(0, 5).map((e) => e.message).join("; ");
46
+ throw new WorkspaceBuilderError(`mount set is invalid: ${summary}`, "invalid-mounts");
47
+ }
48
+ // Make sure all mount targets exist on the host so docker can bind
49
+ // them and so symlinks resolve.
50
+ ensureMountTargets(filesRoot, mounts);
51
+ if (!fs.existsSync(openclawHome)) {
52
+ fs.mkdirSync(openclawHome, { recursive: true });
53
+ }
54
+ const ws = path.join(openclawHome, WORKSPACE_DIR_NAME);
55
+ // Resolve mode: empty list → empty workspace; one root mount →
56
+ // root layout; otherwise named.
57
+ if (mounts.length === 0) {
58
+ clearWorkspace(ws);
59
+ return { layout: "empty", symlinks: {} };
60
+ }
61
+ const rootMount = mounts.find((m) => m.alias === null || m.alias === "");
62
+ if (rootMount) {
63
+ // Root mode is exclusive (validateMounts has already enforced
64
+ // that mounts.length === 1). Workspace itself becomes a symlink.
65
+ const target = path.join(filesRoot, rootMount.path);
66
+ replaceWithSymlink(ws, target);
67
+ return { layout: "root", symlinks: { "": target } };
68
+ }
69
+ // Named mode: workspace is a real directory; each entry is a symlink.
70
+ const symlinks = {};
71
+ if (fs.existsSync(ws)) {
72
+ const lst = fs.lstatSync(ws);
73
+ if (lst.isSymbolicLink()) {
74
+ // Was previously root-mode; remove and recreate as dir
75
+ fs.unlinkSync(ws);
76
+ fs.mkdirSync(ws);
77
+ }
78
+ else if (lst.isDirectory()) {
79
+ pruneSymlinkChildren(ws);
80
+ }
81
+ else {
82
+ throw new WorkspaceBuilderError(`workspace at ${ws} is a file; refusing to clobber`, "non-directory");
83
+ }
84
+ }
85
+ else {
86
+ fs.mkdirSync(ws);
87
+ }
88
+ for (const m of mounts) {
89
+ const alias = effectiveAlias(m);
90
+ if (alias === null)
91
+ continue; // shouldn't happen here (rootMount filtered)
92
+ const target = path.join(filesRoot, m.path);
93
+ const linkPath = path.join(ws, alias);
94
+ fs.symlinkSync(target, linkPath);
95
+ symlinks[alias] = target;
96
+ }
97
+ return { layout: "named", symlinks };
98
+ }
99
+ function clearWorkspace(ws) {
100
+ if (!fs.existsSync(ws)) {
101
+ fs.mkdirSync(ws, { recursive: true });
102
+ return;
103
+ }
104
+ const lst = fs.lstatSync(ws);
105
+ if (lst.isSymbolicLink()) {
106
+ fs.unlinkSync(ws);
107
+ fs.mkdirSync(ws);
108
+ return;
109
+ }
110
+ if (lst.isDirectory()) {
111
+ pruneSymlinkChildren(ws);
112
+ return;
113
+ }
114
+ throw new WorkspaceBuilderError(`workspace at ${ws} is a file; refusing to clobber`, "non-directory");
115
+ }
116
+ /**
117
+ * Remove every entry under ws/ that is a symlink. Refuse if any entry
118
+ * is a plain file or non-symlink directory — that means a previous
119
+ * version had real data here that hasn't been migrated yet (PR-7).
120
+ */
121
+ function pruneSymlinkChildren(ws) {
122
+ for (const name of fs.readdirSync(ws)) {
123
+ const full = path.join(ws, name);
124
+ const lst = fs.lstatSync(full);
125
+ if (lst.isSymbolicLink()) {
126
+ fs.unlinkSync(full);
127
+ }
128
+ else {
129
+ throw new WorkspaceBuilderError(`workspace contains non-symlink entry ${name}; legacy migration required before mount changes`, "needs-migration");
130
+ }
131
+ }
132
+ }
133
+ /** Replace `target` with a symlink to `linkTarget`, safe against existing
134
+ * symlinks (atomic via unlink+symlinkSync). Symlink children of an
135
+ * existing directory are pruned first; plain files trigger the legacy
136
+ * migration error. */
137
+ function replaceWithSymlink(target, linkTarget) {
138
+ if (fs.existsSync(target)) {
139
+ const lst = fs.lstatSync(target);
140
+ if (lst.isSymbolicLink()) {
141
+ fs.unlinkSync(target);
142
+ }
143
+ else if (lst.isDirectory()) {
144
+ // Reuse the same prune-or-throw policy as named mode so the
145
+ // named → root transition is symmetric with named → named.
146
+ pruneSymlinkChildren(target);
147
+ // After pruning, dir must be empty — otherwise pruneSymlinkChildren
148
+ // would have thrown.
149
+ fs.rmdirSync(target);
150
+ }
151
+ else {
152
+ throw new WorkspaceBuilderError(`workspace at ${target} is a regular file; refusing to clobber`, "non-directory");
153
+ }
154
+ }
155
+ fs.symlinkSync(linkTarget, target);
156
+ }
157
+ /**
158
+ * For audit / UI. Given the live workspace, report the layout in
159
+ * structural form without touching it.
160
+ */
161
+ export function describeWorkspace(openclawHome) {
162
+ const ws = path.join(openclawHome, WORKSPACE_DIR_NAME);
163
+ if (!fs.existsSync(ws)) {
164
+ return { exists: false, layout: "empty", symlinks: {} };
165
+ }
166
+ const lst = fs.lstatSync(ws);
167
+ if (lst.isSymbolicLink()) {
168
+ const target = fs.readlinkSync(ws);
169
+ return { exists: true, layout: "root", symlinks: { "": target } };
170
+ }
171
+ if (!lst.isDirectory()) {
172
+ return { exists: true, layout: "empty", symlinks: {} };
173
+ }
174
+ const symlinks = {};
175
+ for (const name of fs.readdirSync(ws)) {
176
+ const full = path.join(ws, name);
177
+ const childLst = fs.lstatSync(full);
178
+ if (!childLst.isSymbolicLink())
179
+ continue;
180
+ symlinks[name] = fs.readlinkSync(full);
181
+ }
182
+ return {
183
+ exists: true,
184
+ layout: Object.keys(symlinks).length === 0 ? "empty" : "named",
185
+ symlinks,
186
+ };
187
+ }
188
+ //# sourceMappingURL=workspace-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-builder.js","sourceRoot":"","sources":["../../src/services/workspace-builder.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -111,6 +111,25 @@ export interface PanelConfig {
111
111
  };
112
112
  telemetry?: false;
113
113
  activation_reported?: boolean;
114
+ /**
115
+ * External mount points — let any host directory appear under
116
+ * ~/.jishushell/files/ as a virtual subtree. The user picks the
117
+ * alias; host_path can be any directory the panel user can read
118
+ * (sensitive system directories are flagged but allowed if the
119
+ * user insists). Mode "ro" forbids any write through the panel
120
+ * or WebDAV; "rw" allows it.
121
+ *
122
+ * Implementation: the panel does NOT create a real symlink in
123
+ * files/ — it virtualizes the path at the FilesManager layer.
124
+ * That avoids the symlink-defense conflict and works identically
125
+ * across docker / raw_exec / process modes.
126
+ */
127
+ external_mounts?: Array<{
128
+ alias: string;
129
+ host_path: string;
130
+ mode: "ro" | "rw";
131
+ description?: string;
132
+ }>;
114
133
  [key: string]: any;
115
134
  }
116
135
  export type AppTaskRuntime = "container" | "process" | "vm";
@@ -126,6 +145,16 @@ export interface AppTaskPort {
126
145
  host_port?: number;
127
146
  container_port?: number;
128
147
  visibility?: AppTaskPortVisibility;
148
+ /**
149
+ * Name of a Nomad `host_network` block declared in nomad.hcl. Default is
150
+ * "external" (eth0 / primary LAN interface). Use "docker_bridge" to
151
+ * publish on the docker0 IP (172.17.0.1) — required when peer containers
152
+ * in the same group dial this port via `host.docker.internal`. Without
153
+ * this, Nomad picks the wrong interface and cross-task
154
+ * host.docker.internal calls get "connection refused" (e.g. weknora-app
155
+ * → paradedb on port 18093).
156
+ */
157
+ host_network?: string;
129
158
  }
130
159
  export interface AppTaskHealth {
131
160
  http?: {
@@ -173,6 +202,19 @@ export interface AppTask {
173
202
  * the unified builder falls back to "host" by default.
174
203
  */
175
204
  user?: string;
205
+ /**
206
+ * Linux capabilities to add back on top of the runtime baseline
207
+ * (`cap_drop: ["ALL"]`). Validated against a tight allowlist
208
+ * (CHOWN / DAC_OVERRIDE / FOWNER / SETUID / SETGID / SETPCAP /
209
+ * NET_BIND_SERVICE) before passing to the docker driver. Required by
210
+ * canonical postgres/redis/-style images whose entrypoint runs as root
211
+ * and uses `gosu`+`chown` to drop privileges to a non-root user before
212
+ * exec'ing the main process. The container still drops to a non-root
213
+ * user before serving traffic — this only re-arms the kernel facilities
214
+ * the drop step needs. Caps outside the allowlist are silently dropped
215
+ * (fail-closed).
216
+ */
217
+ cap_add?: string[];
176
218
  }
177
219
  export interface AppTerminalCommandPreset {
178
220
  cmd: string;
@@ -232,6 +274,24 @@ export interface AppProvide {
232
274
  * should authenticate when calling the endpoint. Optional. Absent = no auth.
233
275
  */
234
276
  auth?: AppProvideAuth;
277
+ /**
278
+ * Controls how the instance detail page generates the iframe `src` for
279
+ * this capability:
280
+ *
281
+ * - `"auto"` (default): direct LAN URL when the published port listens
282
+ * on a non-loopback interface, fall back to panel proxy when only
283
+ * loopback. Best for apps users typically reach by IP+port.
284
+ * - `"proxy"`: always use the panel's same-origin reverse proxy path
285
+ * (`/api/instances/:id/provides/:capability/`). Needed when the
286
+ * upstream is on a network the browser can't dial directly (corp
287
+ * firewall blocks high ports, VPN, mixed-content boundary) or when
288
+ * the upstream emits `X-Frame-Options: SAMEORIGIN`/CSP that block
289
+ * cross-origin iframe embedding — the proxy strips those headers.
290
+ * - `"direct"`: always emit the direct LAN URL, never the proxy path.
291
+ * Useful for upstreams that can't safely run behind a sub-path
292
+ * reverse proxy (some SPAs hardcode absolute asset URLs).
293
+ */
294
+ embedded?: "auto" | "proxy" | "direct";
235
295
  }
236
296
  /**
237
297
  * §6 — describes how a consumer that binds this `provides[]` capability
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Path-scoped advisory locks for the Files module (W1 PR-2).
3
+ *
4
+ * Single-process serialization: all callers using the same `path` string run
5
+ * in arrival order; different paths run independently in parallel.
6
+ *
7
+ * This is *advisory* — it only protects callers that opt in via withPathLock.
8
+ * It does not protect against external writers (Agent, WebDAV, ssh) that
9
+ * touch the file directly. Those paths have their own atomicity (POSIX
10
+ * rename) and the application's tolerance is "POSIX is source of truth".
11
+ *
12
+ * Stale locks (> 60s) are logged but not broken — correctness over recovery.
13
+ */
14
+ /**
15
+ * Run `fn` while holding an advisory lock on `path`.
16
+ *
17
+ * If another caller holds the lock, this call queues behind it; the queue
18
+ * preserves arrival order.
19
+ *
20
+ * Throws (or resolves) propagate from `fn`. The lock is always released.
21
+ */
22
+ export declare function withPathLock<T>(path: string, fn: () => Promise<T>): Promise<T>;
23
+ /**
24
+ * Test-only: clear all locks. Do not use in production.
25
+ */
26
+ export declare function _clearAllPathLocks(): void;
27
+ /**
28
+ * Test-only: number of active locks. Do not use in production.
29
+ */
30
+ export declare function _activePathLockCount(): number;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Path-scoped advisory locks for the Files module (W1 PR-2).
3
+ *
4
+ * Single-process serialization: all callers using the same `path` string run
5
+ * in arrival order; different paths run independently in parallel.
6
+ *
7
+ * This is *advisory* — it only protects callers that opt in via withPathLock.
8
+ * It does not protect against external writers (Agent, WebDAV, ssh) that
9
+ * touch the file directly. Those paths have their own atomicity (POSIX
10
+ * rename) and the application's tolerance is "POSIX is source of truth".
11
+ *
12
+ * Stale locks (> 60s) are logged but not broken — correctness over recovery.
13
+ */
14
+ const locks = new Map();
15
+ const STALE_LOCK_MS = 60_000;
16
+ /**
17
+ * Run `fn` while holding an advisory lock on `path`.
18
+ *
19
+ * If another caller holds the lock, this call queues behind it; the queue
20
+ * preserves arrival order.
21
+ *
22
+ * Throws (or resolves) propagate from `fn`. The lock is always released.
23
+ */
24
+ export async function withPathLock(path, fn) {
25
+ const existing = locks.get(path);
26
+ if (existing && Date.now() - existing.startedAt > STALE_LOCK_MS) {
27
+ console.warn(`[path-lock] stale lock detected for ${path} (held ${Date.now() - existing.startedAt}ms)`);
28
+ }
29
+ const previous = existing?.promise ?? Promise.resolve();
30
+ let release;
31
+ const next = new Promise((resolve) => {
32
+ release = resolve;
33
+ });
34
+ // Our slot in the chain: wait for previous holder, then release `next`
35
+ const ourPromise = previous.then(() => next);
36
+ const entry = { promise: ourPromise, startedAt: Date.now() };
37
+ locks.set(path, entry);
38
+ try {
39
+ await previous;
40
+ // Re-stamp startedAt to "actual hold start" for stale detection
41
+ entry.startedAt = Date.now();
42
+ return await fn();
43
+ }
44
+ finally {
45
+ release();
46
+ if (locks.get(path) === entry) {
47
+ locks.delete(path);
48
+ }
49
+ }
50
+ }
51
+ /**
52
+ * Test-only: clear all locks. Do not use in production.
53
+ */
54
+ export function _clearAllPathLocks() {
55
+ locks.clear();
56
+ }
57
+ /**
58
+ * Test-only: number of active locks. Do not use in production.
59
+ */
60
+ export function _activePathLockCount() {
61
+ return locks.size;
62
+ }
63
+ //# sourceMappingURL=path-locks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-locks.js","sourceRoot":"","sources":["../../src/utils/path-locks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,MAAM,KAAK,GAAG,IAAI,GAAG,EAAqB,CAAC;AAC3C,MAAM,aAAa,GAAG,MAAM,CAAC;AAE7B;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAY,EACZ,EAAoB;IAEpB,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,SAAS,GAAG,aAAa,EAAE,CAAC;QAChE,OAAO,CAAC,IAAI,CACV,uCAAuC,IAAI,UACzC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,SACxB,KAAK,CACN,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,EAAE,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAExD,IAAI,OAAoB,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACzC,OAAO,GAAG,OAAO,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,uEAAuE;IACvE,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAc,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IACxE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAEvB,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC;QACf,gEAAgE;QAChE,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;YAAS,CAAC;QACT,OAAO,EAAE,CAAC;QACV,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,EAAE,CAAC;YAC9B,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,KAAK,CAAC,IAAI,CAAC;AACpB,CAAC"}
@@ -0,0 +1,41 @@
1
+ export declare class PathSafetyError extends Error {
2
+ reason: string;
3
+ constructor(message: string, reason: string);
4
+ }
5
+ /**
6
+ * Validate a user-provided relative path string against common attack
7
+ * vectors. Performs no IO.
8
+ *
9
+ * Accepted:
10
+ * - "" (root)
11
+ * - "notes/x.md", "深/文件.txt", "with-dashes_and.dots"
12
+ *
13
+ * Rejected (PathSafetyError):
14
+ * - leading "/" (absolute)
15
+ * - drive letters ("C:\\foo", "Z:foo")
16
+ * - any "..", ".", or all-dot ("...", "....") segment
17
+ * - empty segments ("a//b")
18
+ * - leading or trailing "/"
19
+ * - backslash anywhere
20
+ * - null bytes or control chars (< 0x20)
21
+ * - segments ending in "." or " " (Windows quirk; some sync tools misbehave)
22
+ * - segment > 255 bytes (ext4 NAME_MAX)
23
+ * - total > 4096 bytes (Linux PATH_MAX)
24
+ * - non-string input
25
+ */
26
+ export declare function validateRelativePath(input: unknown): void;
27
+ /**
28
+ * Resolve a validated relative path inside a fixed root, then verify the
29
+ * resolved path is still inside that root using a separator-explicit prefix
30
+ * check (so `/foo` does not match `/foobar`).
31
+ *
32
+ * Performs no IO. Symlink resolution is the caller's responsibility.
33
+ *
34
+ * @throws PathSafetyError on validation failure or escape attempt
35
+ */
36
+ export declare function resolveSafe(root: string, rel: string): string;
37
+ /**
38
+ * Returns true if any segment of the path begins with ".".
39
+ * Used to optionally hide dotfiles in directory listings.
40
+ */
41
+ export declare function isHidden(input: string): boolean;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Path safety utilities for the Files module (W1).
3
+ *
4
+ * Three layers of defense (per .omc/plans/files-w1.md § 7.1):
5
+ * 1. validateRelativePath — string-level checks (this module, no IO)
6
+ * 2. resolveSafe — path.resolve + prefix check (this module, no IO)
7
+ * 3. realpath/symlink — performed by FilesManager (defense 3, IO)
8
+ *
9
+ * Empty string is the conventional root path and is accepted by both functions.
10
+ */
11
+ import { resolve, sep } from "path";
12
+ const PATH_MAX_BYTES = 4096;
13
+ const NAME_MAX_BYTES = 255;
14
+ export class PathSafetyError extends Error {
15
+ reason;
16
+ constructor(message, reason) {
17
+ super(message);
18
+ this.reason = reason;
19
+ this.name = "PathSafetyError";
20
+ }
21
+ }
22
+ /**
23
+ * Validate a user-provided relative path string against common attack
24
+ * vectors. Performs no IO.
25
+ *
26
+ * Accepted:
27
+ * - "" (root)
28
+ * - "notes/x.md", "深/文件.txt", "with-dashes_and.dots"
29
+ *
30
+ * Rejected (PathSafetyError):
31
+ * - leading "/" (absolute)
32
+ * - drive letters ("C:\\foo", "Z:foo")
33
+ * - any "..", ".", or all-dot ("...", "....") segment
34
+ * - empty segments ("a//b")
35
+ * - leading or trailing "/"
36
+ * - backslash anywhere
37
+ * - null bytes or control chars (< 0x20)
38
+ * - segments ending in "." or " " (Windows quirk; some sync tools misbehave)
39
+ * - segment > 255 bytes (ext4 NAME_MAX)
40
+ * - total > 4096 bytes (Linux PATH_MAX)
41
+ * - non-string input
42
+ */
43
+ export function validateRelativePath(input) {
44
+ if (typeof input !== "string") {
45
+ throw new PathSafetyError("path must be a string", "type");
46
+ }
47
+ if (input === "")
48
+ return; // root, ok
49
+ if (Buffer.byteLength(input, "utf8") > PATH_MAX_BYTES) {
50
+ throw new PathSafetyError("path too long", "path-length");
51
+ }
52
+ if (input.startsWith("/")) {
53
+ throw new PathSafetyError("path must be relative (no leading /)", "absolute");
54
+ }
55
+ if (input.includes("\\")) {
56
+ throw new PathSafetyError("backslash not allowed", "backslash");
57
+ }
58
+ // Windows drive letter: e.g. "C:\\foo", "Z:foo", "Z:/foo"
59
+ if (/^[A-Za-z]:/.test(input)) {
60
+ throw new PathSafetyError("drive letter not allowed", "drive-letter");
61
+ }
62
+ for (let i = 0; i < input.length; i++) {
63
+ const code = input.charCodeAt(i);
64
+ if (code === 0) {
65
+ throw new PathSafetyError("null byte not allowed", "null-byte");
66
+ }
67
+ if (code < 0x20) {
68
+ throw new PathSafetyError("control character not allowed", "control-char");
69
+ }
70
+ }
71
+ if (input.endsWith("/")) {
72
+ throw new PathSafetyError("path must not end with /", "trailing-slash");
73
+ }
74
+ const segments = input.split("/");
75
+ for (const seg of segments) {
76
+ if (seg === "") {
77
+ throw new PathSafetyError("empty path segment", "empty-segment");
78
+ }
79
+ // Reject "." ".." and any all-dots variant ("..." "...." etc.)
80
+ if (/^\.+$/.test(seg)) {
81
+ throw new PathSafetyError(`all-dots segment "${seg}" not allowed`, "dot-segment");
82
+ }
83
+ if (Buffer.byteLength(seg, "utf8") > NAME_MAX_BYTES) {
84
+ throw new PathSafetyError("path segment too long", "segment-length");
85
+ }
86
+ if (seg.endsWith(".") || seg.endsWith(" ")) {
87
+ throw new PathSafetyError(`segment ends with "." or space`, "windows-quirk");
88
+ }
89
+ }
90
+ }
91
+ /**
92
+ * Resolve a validated relative path inside a fixed root, then verify the
93
+ * resolved path is still inside that root using a separator-explicit prefix
94
+ * check (so `/foo` does not match `/foobar`).
95
+ *
96
+ * Performs no IO. Symlink resolution is the caller's responsibility.
97
+ *
98
+ * @throws PathSafetyError on validation failure or escape attempt
99
+ */
100
+ export function resolveSafe(root, rel) {
101
+ validateRelativePath(rel);
102
+ const resolved = resolve(root, rel);
103
+ const rootWithSep = root.endsWith(sep) ? root : root + sep;
104
+ const rootNoSep = root.endsWith(sep) ? root.slice(0, -1) : root;
105
+ if (resolved !== rootNoSep && !resolved.startsWith(rootWithSep)) {
106
+ throw new PathSafetyError("path escapes root", "escape");
107
+ }
108
+ return resolved;
109
+ }
110
+ /**
111
+ * Returns true if any segment of the path begins with ".".
112
+ * Used to optionally hide dotfiles in directory listings.
113
+ */
114
+ export function isHidden(input) {
115
+ if (input === "")
116
+ return false;
117
+ return input.split("/").some(s => s.startsWith("."));
118
+ }
119
+ //# sourceMappingURL=path-safety.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-safety.js","sourceRoot":"","sources":["../../src/utils/path-safety.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { Readable } from "node:stream";
2
+ export interface AtomicWriteOptions {
3
+ /** Compute streaming sha256 of the data while writing. Default: undefined */
4
+ hash?: "sha256";
5
+ /** Expected total size; if set and actual differs, throws. Default: no check */
6
+ expectedSize?: number;
7
+ /** Write mode (POSIX) of the final file. Default 0o644 */
8
+ mode?: number;
9
+ }
10
+ export interface AtomicWriteResult {
11
+ size: number;
12
+ hash?: string;
13
+ }
14
+ /**
15
+ * Stream `source` to `targetPath` atomically.
16
+ *
17
+ * Memory peak is the Node stream high-water-mark (~64KB), regardless of the
18
+ * total file size. Suitable for arbitrarily large files.
19
+ */
20
+ export declare function atomicWriteStream(targetPath: string, source: Readable, opts?: AtomicWriteOptions): Promise<AtomicWriteResult>;
21
+ /**
22
+ * Convenience wrapper for small Buffer writes. Uses the same atomic pipeline.
23
+ */
24
+ export declare function safeWriteBuffer(targetPath: string, buf: Buffer, opts?: AtomicWriteOptions): Promise<AtomicWriteResult>;