@clawscarf/cli 0.1.0-alpha.5 → 0.1.0-alpha.7
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/README.md +8 -0
- package/package.json +2 -1
- package/pnpm-lock.yaml +37 -0
- package/recipes/README.md +1 -1
- package/recipes/team-server/recipe.json +3 -2
- package/release/README.md +19 -0
- package/release/components.json +1 -1
- package/release/telemetry.json +4 -0
- package/runtime/configuration.js +2 -1
- package/runtime/releases/{0.1.0-alpha.5.json → 0.1.0-alpha.7.json} +16 -16
- package/scripts/clawscarf.js +9 -1
- package/scripts/deployment/configuration.js +1 -0
- package/scripts/deployment/connection-settings.js +4 -1
- package/scripts/deployment/launch.js +3 -3
- package/scripts/deployment/network-policy.js +120 -0
- package/scripts/deployment/policy.js +5 -3
- package/scripts/deployment/prepare.js +1 -1
- package/scripts/deployment/public-web.js +176 -0
- package/scripts/installation/command.js +6 -3
- package/scripts/installation/configuration.js +1 -0
- package/scripts/installation/configure.js +1 -0
- package/scripts/installation/installer/collect.js +15 -0
- package/scripts/installation/installer/menu.js +2 -1
- package/scripts/installation/installer/run.js +19 -3
- package/scripts/installation/installer/settings.js +4 -1
- package/scripts/installation/installer/summary.js +1 -0
- package/scripts/installation/options.js +7 -1
- package/scripts/installation/recipes/definition.js +1 -0
- package/scripts/installation/reconfigure.js +10 -2
- package/scripts/installation/resolve.js +1 -0
- package/scripts/installation/setup.js +1 -0
- package/scripts/telemetry.js +282 -0
- package/scripts/deployment/connection-policy.js +0 -80
package/README.md
CHANGED
|
@@ -80,6 +80,9 @@ For automation, the same configuration choices are available as command-line fla
|
|
|
80
80
|
with `--non-interactive`, and commands support `--json`.
|
|
81
81
|
[CLI reference and examples →](https://github.com/clawscarf/clawscarf/blob/main/deploy/deployment/installation.md#configure-without-prompts)
|
|
82
82
|
|
|
83
|
+
See [CLI telemetry](https://github.com/clawscarf/clawscarf/blob/main/deploy/deployment/installation.md#telemetry) for usage reporting
|
|
84
|
+
and how to disable it.
|
|
85
|
+
|
|
83
86
|
## Your server, your team
|
|
84
87
|
|
|
85
88
|
An installation serves **one trusted team**: separate logins and roles, shared
|
|
@@ -87,6 +90,11 @@ execution files and browser accounts. Native roles control application permissio
|
|
|
87
90
|
people allowed to run code share the Gateway’s authority. Use separate installations
|
|
88
91
|
for teams that must be isolated from one another.
|
|
89
92
|
|
|
93
|
+
The Team server recipe enables public web access for agents and shell tools. Operators
|
|
94
|
+
can turn it off; private destinations remain restricted to explicitly configured services.
|
|
95
|
+
Public web access permits sending team data to public services. See the
|
|
96
|
+
[network boundary](https://github.com/clawscarf/clawscarf/blob/main/deploy/openshell/README.md#public-web-access) for enforcement and limits.
|
|
97
|
+
|
|
90
98
|
Default login and optional Connections use **ClawScarf Cloud**. You can use your own
|
|
91
99
|
OIDC provider and disable Connections to run without those hosted services. Original
|
|
92
100
|
model-provider keys and Connections management credentials stay outside OpenClaw.
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.1.0-alpha.
|
|
2
|
+
"version": "0.1.0-alpha.7",
|
|
3
3
|
"engines": {
|
|
4
4
|
"node": ">=24.16.0 <25 || >=26.1.0"
|
|
5
5
|
},
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"node-pg-migrate": "9.0.0",
|
|
12
12
|
"openid-client": "6.8.8",
|
|
13
13
|
"pg": "8.23.0",
|
|
14
|
+
"posthog-node": "5.52.5",
|
|
14
15
|
"proper-lockfile": "4.1.2",
|
|
15
16
|
"undici": "8.10.2",
|
|
16
17
|
"yaml": "2.9.1",
|
package/pnpm-lock.yaml
CHANGED
|
@@ -26,6 +26,9 @@ importers:
|
|
|
26
26
|
pg:
|
|
27
27
|
specifier: 8.23.0
|
|
28
28
|
version: 8.23.0
|
|
29
|
+
posthog-node:
|
|
30
|
+
specifier: 5.52.5
|
|
31
|
+
version: 5.52.5
|
|
29
32
|
proper-lockfile:
|
|
30
33
|
specifier: 4.1.2
|
|
31
34
|
version: 4.1.2
|
|
@@ -556,6 +559,18 @@ packages:
|
|
|
556
559
|
integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==
|
|
557
560
|
}
|
|
558
561
|
|
|
562
|
+
'@posthog/core@1.55.1':
|
|
563
|
+
resolution:
|
|
564
|
+
{
|
|
565
|
+
integrity: sha512-S76bSbCHGVC8oQa1zyue6A6WzkNf1Ue4oRvfvBNLRnFM3UoWnpWnQ4T/4IzrIEtVCQYv1OI7gk8uL8/U6Tuu7w==
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
'@posthog/types@1.412.4':
|
|
569
|
+
resolution:
|
|
570
|
+
{
|
|
571
|
+
integrity: sha512-Q7lV9O9TbLngjOYw1ucm3bS3tn48xb1B4ZQdDy8gv7yfKe99hCTeWYG4xZ36nJM80VxpTF9TriiJt5hngDOkBg==
|
|
572
|
+
}
|
|
573
|
+
|
|
559
574
|
'@seriousme/openapi-schema-validator@2.9.1':
|
|
560
575
|
resolution:
|
|
561
576
|
{
|
|
@@ -1917,6 +1932,18 @@ packages:
|
|
|
1917
1932
|
}
|
|
1918
1933
|
engines: { node: '>=0.10.0' }
|
|
1919
1934
|
|
|
1935
|
+
posthog-node@5.52.5:
|
|
1936
|
+
resolution:
|
|
1937
|
+
{
|
|
1938
|
+
integrity: sha512-r4KRXh2MvcHYh1s3NUca022GqujkJjpmD4qbRzWOLVTu8BfN3fqZj+MyKIxvHPXsS8sKsNVX+MlKk2qzW0Y4GQ==
|
|
1939
|
+
}
|
|
1940
|
+
engines: { node: ^20.20.0 || >=22.22.0 }
|
|
1941
|
+
peerDependencies:
|
|
1942
|
+
rxjs: ^7.0.0
|
|
1943
|
+
peerDependenciesMeta:
|
|
1944
|
+
rxjs:
|
|
1945
|
+
optional: true
|
|
1946
|
+
|
|
1920
1947
|
powershell-utils@0.1.0:
|
|
1921
1948
|
resolution:
|
|
1922
1949
|
{
|
|
@@ -2714,6 +2741,12 @@ snapshots:
|
|
|
2714
2741
|
|
|
2715
2742
|
'@pinojs/redact@0.4.0': {}
|
|
2716
2743
|
|
|
2744
|
+
'@posthog/core@1.55.1':
|
|
2745
|
+
dependencies:
|
|
2746
|
+
'@posthog/types': 1.412.4
|
|
2747
|
+
|
|
2748
|
+
'@posthog/types@1.412.4': {}
|
|
2749
|
+
|
|
2717
2750
|
'@seriousme/openapi-schema-validator@2.9.1':
|
|
2718
2751
|
dependencies:
|
|
2719
2752
|
ajv: 8.20.0
|
|
@@ -3526,6 +3559,10 @@ snapshots:
|
|
|
3526
3559
|
dependencies:
|
|
3527
3560
|
xtend: 4.0.2
|
|
3528
3561
|
|
|
3562
|
+
posthog-node@5.52.5:
|
|
3563
|
+
dependencies:
|
|
3564
|
+
'@posthog/core': 1.55.1
|
|
3565
|
+
|
|
3529
3566
|
powershell-utils@0.1.0: {}
|
|
3530
3567
|
|
|
3531
3568
|
prelude-ls@1.2.1: {}
|
package/recipes/README.md
CHANGED
|
@@ -21,7 +21,7 @@ no executable hooks, secrets or user identities.
|
|
|
21
21
|
|
|
22
22
|
- `models` selects model ID, provider and reasoning from the bundled
|
|
23
23
|
[model catalog](../deploy/models/catalog.json).
|
|
24
|
-
- `defaults` selects resources, browser and Connections enablement.
|
|
24
|
+
- `defaults` selects resources, public web access, browser and Connections enablement.
|
|
25
25
|
- `packs: [{ id, members }]` selects agents from the bundled [packs](../packs/README.md).
|
|
26
26
|
The installer checks members and collects required credentials after settings review.
|
|
27
27
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "team-server",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"runtime": "../../runtime/releases/0.1.0-alpha.
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"runtime": "../../runtime/releases/0.1.0-alpha.7.json",
|
|
5
5
|
"name": "Team server",
|
|
6
6
|
"description": "A protected OpenClaw server for your team, with hosted login, managed models and optional Connections.",
|
|
7
7
|
"maturity": "supported",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"browser": {
|
|
16
16
|
"enabled": false
|
|
17
17
|
},
|
|
18
|
+
"publicWeb": true,
|
|
18
19
|
"connections": {
|
|
19
20
|
"enabled": true
|
|
20
21
|
}
|
package/release/README.md
CHANGED
|
@@ -159,3 +159,22 @@ an existing output directory and removes incomplete output on failure. It does n
|
|
|
159
159
|
build images, copy recipes/packs or publish artifacts. A custom recipe can point to
|
|
160
160
|
the resulting runtime definition. The [component pins](components.json) record
|
|
161
161
|
upstream sources; [operator packaging](operator.md) describes the CLI archive.
|
|
162
|
+
|
|
163
|
+
## CLI telemetry destination
|
|
164
|
+
|
|
165
|
+
[telemetry.json](telemetry.json) is copied unchanged into the compiled operator,
|
|
166
|
+
npm CLI and standalone CLI payload. It selects the ClawScarf project in PostHog EU
|
|
167
|
+
using `host` (the HTTPS ingestion origin) and `projectToken` (the public `phc_`
|
|
168
|
+
project token). Setting the file to `null` disables reporting for that build.
|
|
169
|
+
Never put a personal or project-secret API key in this public file. A loopback
|
|
170
|
+
HTTP origin is accepted for local receiver tests only.
|
|
171
|
+
|
|
172
|
+
The [CLI telemetry guide](../deploy/deployment/installation.md#telemetry) owns event
|
|
173
|
+
fields, user opt-out, local identity and delivery limits. Destination changes need
|
|
174
|
+
a new CLI build/package; they do not change running team servers or cloud-service
|
|
175
|
+
configuration. Automated tests use local receivers. The destination project's
|
|
176
|
+
**Settings → Privacy → Discard client IP data** must stay enabled: a null `$ip`
|
|
177
|
+
property alone does not prevent PostHog from storing the connection IP. The CLI
|
|
178
|
+
also disables GeoIP enrichment. When changing destinations, verify that privacy
|
|
179
|
+
setting and confirm start/finish events and their properties in the target project
|
|
180
|
+
before publishing. An ingestion check does not establish CLI release publication.
|
package/release/components.json
CHANGED
package/runtime/configuration.js
CHANGED
|
@@ -35,6 +35,7 @@ export function initialConfiguration(input) {
|
|
|
35
35
|
bind: "loopback",
|
|
36
36
|
port: 18789,
|
|
37
37
|
terminal: { enabled: false },
|
|
38
|
+
cliAgents: { enabled: false },
|
|
38
39
|
trustedProxies: ["127.0.0.1"],
|
|
39
40
|
controlUi: {
|
|
40
41
|
allowedOrigins: [settings.publicOrigin],
|
|
@@ -63,7 +64,7 @@ export function initialConfiguration(input) {
|
|
|
63
64
|
member: {
|
|
64
65
|
agents: "*",
|
|
65
66
|
scopes: ["operator.read", "operator.write", "operator.talk"],
|
|
66
|
-
sessions: { others: "
|
|
67
|
+
sessions: { others: "view" },
|
|
67
68
|
sandbox: "inherit",
|
|
68
69
|
},
|
|
69
70
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
4
|
-
"sourceRevision": "
|
|
3
|
+
"version": "0.1.0-alpha.7",
|
|
4
|
+
"sourceRevision": "681655cc3e931209cb56b9f6866c9d780055c709",
|
|
5
5
|
"platforms": [
|
|
6
6
|
"darwin-arm64",
|
|
7
7
|
"linux-arm64",
|
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
],
|
|
10
10
|
"images": {
|
|
11
11
|
"postgres": "postgres@sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193",
|
|
12
|
-
"gateway": "ghcr.io/clawscarf/clawscarf/runtime@sha256:
|
|
13
|
-
"companion": "ghcr.io/clawscarf/clawscarf/companion@sha256:
|
|
14
|
-
"openshellClient": "ghcr.io/clawscarf/clawscarf/openshell-client@sha256:
|
|
15
|
-
"relay": "ghcr.io/clawscarf/clawscarf/browser-relay@sha256:
|
|
12
|
+
"gateway": "ghcr.io/clawscarf/clawscarf/runtime@sha256:05a2341c36ca49d4a83d24a5f3bffff74dd54944b9fc3e13b7a5113d9ecda5f0",
|
|
13
|
+
"companion": "ghcr.io/clawscarf/clawscarf/companion@sha256:94a134f1f88eaacc785d1f0b5d7715725506f5c2e9f1d822916ada86513fb3aa",
|
|
14
|
+
"openshellClient": "ghcr.io/clawscarf/clawscarf/openshell-client@sha256:1ed1c3db878f5dc11045442319e3ff667aaebbf98977e042ace7486aff6a5b7a",
|
|
15
|
+
"relay": "ghcr.io/clawscarf/clawscarf/browser-relay@sha256:96101feea8be108c25eb2df1847a99b4a4cab2ca632d9895b36ea665bbd5f5ac",
|
|
16
16
|
"models": "ghcr.io/berriai/litellm:v1.100.1@sha256:a3715fa7ad8387941ab697259bd2881d68931657247a41984f90fae6d11c62bf",
|
|
17
17
|
"browser": {
|
|
18
|
-
"chromium": "ghcr.io/clawscarf/clawscarf/browser@sha256:
|
|
19
|
-
"node": "ghcr.io/clawscarf/clawscarf/browser-node@sha256:
|
|
20
|
-
"dns": "ghcr.io/clawscarf/clawscarf/browser-dns@sha256:
|
|
21
|
-
"egress": "ghcr.io/clawscarf/clawscarf/browser-egress@sha256:
|
|
18
|
+
"chromium": "ghcr.io/clawscarf/clawscarf/browser@sha256:c0b9a423829a990c805fdee6af55597ec02a9dc0a8aed35590fc43a75d5fe60f",
|
|
19
|
+
"node": "ghcr.io/clawscarf/clawscarf/browser-node@sha256:2102a3bb15cb8c828ae09c91271f67b7e963d8bacd21f848044782e7c33f9d42",
|
|
20
|
+
"dns": "ghcr.io/clawscarf/clawscarf/browser-dns@sha256:bc877b4bc0635a1dd4b4d3dba11e1142a38d0c1ff51bb90fc58ebf42fa57562d",
|
|
21
|
+
"egress": "ghcr.io/clawscarf/clawscarf/browser-egress@sha256:7519c3dc6c54afadf5104ae92ecc5f6af6983fb308e16e2c81b7df593a8c6f8d"
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"tools": {
|
|
@@ -28,34 +28,34 @@
|
|
|
28
28
|
"darwin-arm64": {
|
|
29
29
|
"file": "tools/darwin-arm64/openshell",
|
|
30
30
|
"sha256": "0baeeffe0e4c184b08fde910ac9e8815fb0b8cfb808e49c0e634d7db65fb03bb",
|
|
31
|
-
"url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.
|
|
31
|
+
"url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.7/openshell-darwin-arm64"
|
|
32
32
|
},
|
|
33
33
|
"linux-arm64": {
|
|
34
34
|
"file": "tools/linux-arm64/openshell",
|
|
35
35
|
"sha256": "84df9f059b16b66c83748606cc87d9edcda84b57effea19543f5c368aba7eb18",
|
|
36
|
-
"url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.
|
|
36
|
+
"url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.7/openshell-linux-arm64"
|
|
37
37
|
},
|
|
38
38
|
"linux-x64": {
|
|
39
39
|
"file": "tools/linux-x64/openshell",
|
|
40
40
|
"sha256": "24e85062073d512d1951c76cd3890b7bce1ab01cc36a08f88b3b43c067a402da",
|
|
41
|
-
"url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.
|
|
41
|
+
"url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.7/openshell-linux-x64"
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"gateway": {
|
|
45
45
|
"darwin-arm64": {
|
|
46
46
|
"file": "tools/darwin-arm64/openshell-gateway",
|
|
47
47
|
"sha256": "298706259ef18350f334c222e981e91583558511d50bce22727c689d261203e6",
|
|
48
|
-
"url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.
|
|
48
|
+
"url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.7/openshell-gateway-darwin-arm64"
|
|
49
49
|
},
|
|
50
50
|
"linux-arm64": {
|
|
51
51
|
"file": "tools/linux-arm64/openshell-gateway",
|
|
52
52
|
"sha256": "0e660428e655e7481f34ba8a4b351554400baf791863d32555c98dddb3f09ae2",
|
|
53
|
-
"url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.
|
|
53
|
+
"url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.7/openshell-gateway-linux-arm64"
|
|
54
54
|
},
|
|
55
55
|
"linux-x64": {
|
|
56
56
|
"file": "tools/linux-x64/openshell-gateway",
|
|
57
57
|
"sha256": "780fc7ad871e2163f3fb866a2d0254ee682fc0d83e6261a84b350c8dcdd9f47b",
|
|
58
|
-
"url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.
|
|
58
|
+
"url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.7/openshell-gateway-linux-x64"
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
}
|
package/scripts/clawscarf.js
CHANGED
|
@@ -8,7 +8,11 @@ import { LocalSetupError } from "./deployment/process.js";
|
|
|
8
8
|
import { LocalDatabaseError } from "./deployment/database.js";
|
|
9
9
|
import { InstallerCancelled } from "./installation/installer/prompts.js";
|
|
10
10
|
import { installationCommand } from "./installation/command.js";
|
|
11
|
-
|
|
11
|
+
import { CliTelemetry } from "./telemetry.js";
|
|
12
|
+
const telemetry = new CliTelemetry();
|
|
13
|
+
const program = installationCommand(telemetry);
|
|
14
|
+
program.hook("preAction", async (_program, command) => telemetry.start(command));
|
|
15
|
+
let failureCode;
|
|
12
16
|
const commands = [program];
|
|
13
17
|
for (const command of commands) {
|
|
14
18
|
command.exitOverride().configureOutput({ outputError: () => { } });
|
|
@@ -85,8 +89,12 @@ catch (error) {
|
|
|
85
89
|
? error.message
|
|
86
90
|
: "Check the configuration, file permissions and local prerequisites. No operation was automatically retried."),
|
|
87
91
|
};
|
|
92
|
+
failureCode = failure.code;
|
|
88
93
|
process.stderr.write((program.opts().json
|
|
89
94
|
? JSON.stringify(failure)
|
|
90
95
|
: `Error: ${failure.detail}`) + "\n");
|
|
91
96
|
process.exitCode = error instanceof InstallerCancelled ? 130 : 1;
|
|
92
97
|
}
|
|
98
|
+
finally {
|
|
99
|
+
await telemetry.finish(process.exitCode, failureCode);
|
|
100
|
+
}
|
|
@@ -5,6 +5,7 @@ import { companionConfiguration } from "./configuration.js";
|
|
|
5
5
|
import { loadInitialConnections, prepareInitialConnections, readInitialConnectionToken, } from "./connections.js";
|
|
6
6
|
import { configureConnectionsVolume } from "./connections-runtime.js";
|
|
7
7
|
import { writePrivate } from "./state.js";
|
|
8
|
+
import { stageNetworkPolicyChange } from "./network-policy.js";
|
|
8
9
|
import { initialRuntimePolicy } from "./policy.js";
|
|
9
10
|
/** Explicit capability edit, with the installation stopped and its operator lock held. */
|
|
10
11
|
export async function applyConnectionSettings(directory, state, previous, credentialFile) {
|
|
@@ -56,5 +57,7 @@ export async function applyConnectionSettings(directory, state, previous, creden
|
|
|
56
57
|
await writePrivate(file, JSON.stringify(config));
|
|
57
58
|
const rule = initialRuntimePolicy("network_policies: {}", undefined, endpoint)
|
|
58
59
|
.network_policies.connections_broker ?? null;
|
|
59
|
-
await
|
|
60
|
+
await stageNetworkPolicyChange(directory, state, {
|
|
61
|
+
connections_broker: rule,
|
|
62
|
+
});
|
|
60
63
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { applyNetworkPolicy } from "./network-policy.js";
|
|
2
2
|
import { verifyRuntimeImage } from "./images.js";
|
|
3
3
|
import { startBrowserNode } from "./browser-node-pairing.js";
|
|
4
4
|
import { verifyBrowserNode } from "./browser-node.js";
|
|
@@ -83,7 +83,7 @@ export async function launchLocal(directoryInput, report, control = {}) {
|
|
|
83
83
|
await waitFor(async () => {
|
|
84
84
|
await run(state.input.openshellCli, ["sandbox", "list", "--gateway", name, "--output", "json"], { env, timeout: 5000 });
|
|
85
85
|
});
|
|
86
|
-
await
|
|
86
|
+
await applyNetworkPolicy(directory, state, env);
|
|
87
87
|
if (state.input.modelGateway) {
|
|
88
88
|
report("Starting model gateway…");
|
|
89
89
|
await compose(directory, [
|
|
@@ -113,7 +113,7 @@ export async function launchLocal(directoryInput, report, control = {}) {
|
|
|
113
113
|
await response.body?.cancel();
|
|
114
114
|
});
|
|
115
115
|
await verifyRuntimeBinding(state, runtime);
|
|
116
|
-
await
|
|
116
|
+
await waitFor(() => applyNetworkPolicy(directory, state, env, true));
|
|
117
117
|
if (state.input.browser) {
|
|
118
118
|
report("Starting native browser node…");
|
|
119
119
|
await startBrowserNode(directory, state, cancellation.signal);
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { readFile, rm } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { run } from "./process.js";
|
|
6
|
+
import { resourceNames, writePrivate } from "./state.js";
|
|
7
|
+
import { runtimeManager } from "./runtime.js";
|
|
8
|
+
const connectionRuleSchema = z.object({
|
|
9
|
+
name: z.string(),
|
|
10
|
+
endpoints: z.array(z.object({ host: z.string(), port: z.number(), tls: z.string() })),
|
|
11
|
+
binaries: z.array(z.object({ path: z.string() })),
|
|
12
|
+
});
|
|
13
|
+
const publicRuleSchema = z.object({
|
|
14
|
+
name: z.string(),
|
|
15
|
+
endpoints: z.array(z.object({
|
|
16
|
+
ports: z.array(z.number()),
|
|
17
|
+
allowed_ips: z.array(z.string()),
|
|
18
|
+
tls: z.string(),
|
|
19
|
+
})),
|
|
20
|
+
binaries: z.array(z.object({ path: z.string() })),
|
|
21
|
+
});
|
|
22
|
+
const changesSchema = z.strictObject({
|
|
23
|
+
connections_broker: connectionRuleSchema.nullable().optional(),
|
|
24
|
+
public_web: publicRuleSchema.nullable().optional(),
|
|
25
|
+
});
|
|
26
|
+
export async function stageNetworkPolicyChange(directory, state, rules) {
|
|
27
|
+
const file = join(directory, "private/network-policy-change.json");
|
|
28
|
+
let previous = {};
|
|
29
|
+
try {
|
|
30
|
+
previous = z
|
|
31
|
+
.strictObject({ ownerId: z.literal(state.ownerId), rules: changesSchema })
|
|
32
|
+
.parse(JSON.parse(await readFile(file, "utf8"))).rules;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
await writePrivate(file, JSON.stringify({
|
|
39
|
+
ownerId: state.ownerId,
|
|
40
|
+
rules: changesSchema.parse({ ...previous, ...rules }),
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
const policySchema = z.looseObject({
|
|
44
|
+
network_policies: z.record(z.string(), z.unknown()),
|
|
45
|
+
});
|
|
46
|
+
/** Apply only explicitly selected managed rules; never regenerate an operator's other policies. */
|
|
47
|
+
export async function applyNetworkPolicy(directory, state, env, verify = false, command = run) {
|
|
48
|
+
const pending = join(directory, "private/network-policy-change.json");
|
|
49
|
+
let text;
|
|
50
|
+
try {
|
|
51
|
+
text = await readFile(pending, "utf8");
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
55
|
+
return;
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
const change = z
|
|
59
|
+
.strictObject({
|
|
60
|
+
ownerId: z.literal(state.ownerId),
|
|
61
|
+
rules: changesSchema,
|
|
62
|
+
})
|
|
63
|
+
.parse(JSON.parse(text));
|
|
64
|
+
const base = join(directory, "private/runtime-policy.json");
|
|
65
|
+
const update = (policy) => {
|
|
66
|
+
return {
|
|
67
|
+
...policy,
|
|
68
|
+
network_policies: Object.fromEntries([
|
|
69
|
+
...Object.entries(policy.network_policies).filter(([key]) => !Object.hasOwn(change.rules, key)),
|
|
70
|
+
...Object.entries(change.rules).filter(([, rule]) => rule !== null),
|
|
71
|
+
]),
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
const matches = (policy) => Object.entries(change.rules).every(([key, rule]) => {
|
|
75
|
+
const observed = policy.network_policies[key];
|
|
76
|
+
if (rule === null)
|
|
77
|
+
return observed === undefined;
|
|
78
|
+
const parsed = (key === "public_web" ? publicRuleSchema : connectionRuleSchema).safeParse(observed);
|
|
79
|
+
return parsed.success && isDeepStrictEqual(parsed.data, rule);
|
|
80
|
+
});
|
|
81
|
+
const name = resourceNames(state).sandbox;
|
|
82
|
+
if (!(await runtimeManager(directory, state, env, command).recorded()).receipt) {
|
|
83
|
+
if (verify)
|
|
84
|
+
throw Error("The configured runtime has not been recorded.");
|
|
85
|
+
await writePrivate(base, JSON.stringify(update(policySchema.parse(JSON.parse(await readFile(base, "utf8"))))));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const observed = z
|
|
89
|
+
.object({
|
|
90
|
+
sandbox: z.literal(name),
|
|
91
|
+
policy_source: z.literal("sandbox"),
|
|
92
|
+
version: z.number().int(),
|
|
93
|
+
active_version: z.number().int(),
|
|
94
|
+
status: z.string(),
|
|
95
|
+
policy: policySchema,
|
|
96
|
+
})
|
|
97
|
+
.parse(JSON.parse(await command(state.input.openshellCli, [
|
|
98
|
+
"policy",
|
|
99
|
+
"get",
|
|
100
|
+
name,
|
|
101
|
+
"--gateway",
|
|
102
|
+
name,
|
|
103
|
+
"--base",
|
|
104
|
+
"--output",
|
|
105
|
+
"json",
|
|
106
|
+
], { env })));
|
|
107
|
+
if (matches(observed.policy)) {
|
|
108
|
+
if (verify) {
|
|
109
|
+
if (observed.active_version !== observed.version ||
|
|
110
|
+
observed.status !== "effective")
|
|
111
|
+
throw Error("The selected network policy has not become effective in the runtime.");
|
|
112
|
+
await rm(pending);
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (verify)
|
|
117
|
+
throw Error("The selected network policy was not confirmed. The installation remains unavailable.");
|
|
118
|
+
await writePrivate(base, JSON.stringify(update(observed.policy)));
|
|
119
|
+
await command(state.input.openshellCli, ["policy", "set", name, "--gateway", name, "--policy", base], { env });
|
|
120
|
+
}
|
|
@@ -4,7 +4,8 @@ import { parseDocument } from "yaml";
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { LocalSetupError } from "./process.js";
|
|
6
6
|
import { ensurePrivateFile } from "./state.js";
|
|
7
|
-
|
|
7
|
+
import { publicWebPolicy } from "./public-web.js";
|
|
8
|
+
export function initialRuntimePolicy(source, models, connections, publicWeb = false) {
|
|
8
9
|
const document = parseDocument(source);
|
|
9
10
|
if (document.errors.length || document.warnings.length)
|
|
10
11
|
throw new LocalSetupError("invalid_runtime_policy", "The shipped runtime policy is invalid.");
|
|
@@ -17,6 +18,7 @@ export function initialRuntimePolicy(source, models, connections) {
|
|
|
17
18
|
return {
|
|
18
19
|
...parsed.data,
|
|
19
20
|
network_policies: {
|
|
21
|
+
...(publicWeb ? { public_web: publicWebPolicy(true) } : {}),
|
|
20
22
|
...(connections
|
|
21
23
|
? {
|
|
22
24
|
connections_broker: {
|
|
@@ -50,7 +52,7 @@ export function initialRuntimePolicy(source, models, connections) {
|
|
|
50
52
|
},
|
|
51
53
|
};
|
|
52
54
|
}
|
|
53
|
-
export async function prepareRuntimePolicy(directory, models, connections) {
|
|
54
|
-
const policy = initialRuntimePolicy(await readFile(new URL("../../deploy/openshell/policy.yaml", import.meta.url), "utf8"), models, connections);
|
|
55
|
+
export async function prepareRuntimePolicy(directory, models, connections, publicWeb = false) {
|
|
56
|
+
const policy = initialRuntimePolicy(await readFile(new URL("../../deploy/openshell/policy.yaml", import.meta.url), "utf8"), models, connections, publicWeb);
|
|
55
57
|
await ensurePrivateFile(join(directory, "private/runtime-policy.json"), JSON.stringify(policy));
|
|
56
58
|
}
|
|
@@ -93,7 +93,7 @@ export async function prepareLocal(directoryInput, inputValue, capabilities) {
|
|
|
93
93
|
: 0), null, 2));
|
|
94
94
|
await prepareModelCredential(directory, state);
|
|
95
95
|
const models = await prepareInitialModels(directory, input.models);
|
|
96
|
-
await prepareRuntimePolicy(directory, models, connectionsEndpoint);
|
|
96
|
+
await prepareRuntimePolicy(directory, models, connectionsEndpoint, state.input.publicWeb);
|
|
97
97
|
await withPreparedDatabase(directory, state, async (pool, runtimeUrl) => {
|
|
98
98
|
const store = new PostgresAccessStore(pool, await readFile(join(privateDirectory, "encryption.key")), {
|
|
99
99
|
issuer: input.team.issuer,
|