alasio 8.0.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/cli/src/api-server.js +18 -0
  2. package/dist/cli/src/api-server.js.map +1 -0
  3. package/dist/cli/src/branches.js +61 -20
  4. package/dist/cli/src/branches.js.map +1 -1
  5. package/dist/cli/src/cluster/docker.js +25 -10
  6. package/dist/cli/src/cluster/docker.js.map +1 -1
  7. package/dist/cli/src/cluster/host.js +33 -7
  8. package/dist/cli/src/cluster/host.js.map +1 -1
  9. package/dist/cli/src/cluster/k3s.js +43 -11
  10. package/dist/cli/src/cluster/k3s.js.map +1 -1
  11. package/dist/cli/src/cluster/node.js +107 -32
  12. package/dist/cli/src/cluster/node.js.map +1 -1
  13. package/dist/cli/src/cluster/stargz.js +55 -0
  14. package/dist/cli/src/cluster/stargz.js.map +1 -0
  15. package/dist/cli/src/commands/branch.js +5 -4
  16. package/dist/cli/src/commands/branch.js.map +1 -1
  17. package/dist/cli/src/commands/common.js +9 -3
  18. package/dist/cli/src/commands/common.js.map +1 -1
  19. package/dist/cli/src/commands/folder.js +14 -0
  20. package/dist/cli/src/commands/folder.js.map +1 -0
  21. package/dist/cli/src/commands/status.js +64 -12
  22. package/dist/cli/src/commands/status.js.map +1 -1
  23. package/dist/cli/src/commands/uninstall.js +8 -9
  24. package/dist/cli/src/commands/uninstall.js.map +1 -1
  25. package/dist/cli/src/commands/up.js +11 -3
  26. package/dist/cli/src/commands/up.js.map +1 -1
  27. package/dist/cli/src/commands/upgrade.js +1 -1
  28. package/dist/cli/src/commands/upgrade.js.map +1 -1
  29. package/dist/cli/src/commands.js +2 -1
  30. package/dist/cli/src/commands.js.map +1 -1
  31. package/dist/cli/src/config.js +4 -1
  32. package/dist/cli/src/config.js.map +1 -1
  33. package/dist/cli/src/cpu-baseline.js +8 -4
  34. package/dist/cli/src/cpu-baseline.js.map +1 -1
  35. package/dist/cli/src/folders.js +67 -0
  36. package/dist/cli/src/folders.js.map +1 -0
  37. package/dist/cli/src/gateway-calls.js +58 -0
  38. package/dist/cli/src/gateway-calls.js.map +1 -0
  39. package/dist/cli/src/install.js +77 -29
  40. package/dist/cli/src/install.js.map +1 -1
  41. package/dist/cli/src/kept-releases.js +27 -20
  42. package/dist/cli/src/kept-releases.js.map +1 -1
  43. package/dist/cli/src/kube/api.js +2 -3
  44. package/dist/cli/src/kube/api.js.map +1 -1
  45. package/dist/cli/src/kube/apply.js +15 -72
  46. package/dist/cli/src/kube/apply.js.map +1 -1
  47. package/dist/cli/src/kube/rollout.js +28 -16
  48. package/dist/cli/src/kube/rollout.js.map +1 -1
  49. package/dist/cli/src/manifests/admission.js +342 -223
  50. package/dist/cli/src/manifests/admission.js.map +1 -1
  51. package/dist/cli/src/manifests/alasio.js +58 -394
  52. package/dist/cli/src/manifests/alasio.js.map +1 -1
  53. package/dist/cli/src/manifests/branch.js +10 -26
  54. package/dist/cli/src/manifests/branch.js.map +1 -1
  55. package/dist/cli/src/manifests/collector.js +60 -44
  56. package/dist/cli/src/manifests/collector.js.map +1 -1
  57. package/dist/cli/src/manifests/common.js +32 -2
  58. package/dist/cli/src/manifests/common.js.map +1 -1
  59. package/dist/cli/src/manifests/config.js +64 -81
  60. package/dist/cli/src/manifests/config.js.map +1 -1
  61. package/dist/cli/src/manifests/disruption.js +10 -0
  62. package/dist/cli/src/manifests/disruption.js.map +1 -1
  63. package/dist/cli/src/manifests/folders.js +128 -0
  64. package/dist/cli/src/manifests/folders.js.map +1 -0
  65. package/dist/cli/src/manifests/grafana.js +4 -2
  66. package/dist/cli/src/manifests/grafana.js.map +1 -1
  67. package/dist/cli/src/manifests/index.js +10 -7
  68. package/dist/cli/src/manifests/index.js.map +1 -1
  69. package/dist/cli/src/manifests/lake.js +18 -4
  70. package/dist/cli/src/manifests/lake.js.map +1 -1
  71. package/dist/cli/src/manifests/log.js +32 -14
  72. package/dist/cli/src/manifests/log.js.map +1 -1
  73. package/dist/cli/src/manifests/neon.js +3 -3
  74. package/dist/cli/src/manifests/neon.js.map +1 -1
  75. package/dist/cli/src/manifests/network-policies.js +140 -74
  76. package/dist/cli/src/manifests/network-policies.js.map +1 -1
  77. package/dist/cli/src/manifests/node-agent.js +11 -4
  78. package/dist/cli/src/manifests/node-agent.js.map +1 -1
  79. package/dist/cli/src/manifests/object-store.js +4 -2
  80. package/dist/cli/src/manifests/object-store.js.map +1 -1
  81. package/dist/cli/src/manifests/sandbox-gateway.js +663 -0
  82. package/dist/cli/src/manifests/sandbox-gateway.js.map +1 -0
  83. package/dist/cli/src/manifests/scalers.js +16 -5
  84. package/dist/cli/src/manifests/scalers.js.map +1 -1
  85. package/dist/cli/src/manifests/sizing.js +16 -8
  86. package/dist/cli/src/manifests/sizing.js.map +1 -1
  87. package/dist/cli/src/manifests/valkey.js +11 -5
  88. package/dist/cli/src/manifests/valkey.js.map +1 -1
  89. package/dist/cli/src/manifests/vpa.js +3 -1
  90. package/dist/cli/src/manifests/vpa.js.map +1 -1
  91. package/dist/cli/src/manifests/workspace-storage.js +174 -89
  92. package/dist/cli/src/manifests/workspace-storage.js.map +1 -1
  93. package/dist/cli/src/neon-storage.js +54 -14
  94. package/dist/cli/src/neon-storage.js.map +1 -1
  95. package/dist/cli/src/release.js +9 -9
  96. package/dist/cluster/node/pins.json +9 -4
  97. package/dist/neon/control/branches.js +6 -6
  98. package/dist/neon/control/branches.js.map +1 -1
  99. package/dist/neon/control/secrets.js +14 -3
  100. package/dist/neon/control/secrets.js.map +1 -1
  101. package/dist/src/binder/names.js +33 -0
  102. package/dist/src/binder/names.js.map +1 -0
  103. package/dist/src/binder/tokens.js +58 -0
  104. package/dist/src/binder/tokens.js.map +1 -0
  105. package/dist/src/branch/names.js +12 -2
  106. package/dist/src/branch/names.js.map +1 -1
  107. package/dist/src/events/names.js +7 -0
  108. package/dist/src/events/names.js.map +1 -0
  109. package/dist/src/gateway/names.js +201 -0
  110. package/dist/src/gateway/names.js.map +1 -0
  111. package/dist/src/harness/names.js +49 -0
  112. package/dist/src/harness/names.js.map +1 -0
  113. package/dist/src/kube/sandbox-shape.js +8 -68
  114. package/dist/src/kube/sandbox-shape.js.map +1 -1
  115. package/dist/src/node-agent/names.js +52 -2
  116. package/dist/src/node-agent/names.js.map +1 -1
  117. package/dist/src/sandbox/names.js +12 -20
  118. package/dist/src/sandbox/names.js.map +1 -1
  119. package/dist/src/sandbox/otlp-resource.js +173 -0
  120. package/dist/src/sandbox/otlp-resource.js.map +1 -0
  121. package/dist/src/sandbox/telemetry.js +43 -0
  122. package/dist/src/sandbox/telemetry.js.map +1 -0
  123. package/dist/src/telemetry/config.js +131 -0
  124. package/dist/src/telemetry/config.js.map +1 -0
  125. package/dist/src/telemetry/forward.js +154 -0
  126. package/dist/src/telemetry/forward.js.map +1 -0
  127. package/package.json +1 -1
  128. package/dist/cli/src/manifests/agent-sandbox.js +0 -92
  129. package/dist/cli/src/manifests/agent-sandbox.js.map +0 -1
  130. package/dist/cli/src/manifests/juicefs-csi.js +0 -320
  131. package/dist/cli/src/manifests/juicefs-csi.js.map +0 -1
  132. package/dist/cli/src/manifests/sandboxes.agents.x-k8s.io.json +0 -6295
  133. package/dist/src/kube/config.js +0 -145
  134. package/dist/src/kube/config.js.map +0 -1
@@ -1,283 +1,37 @@
1
- /**
2
- * alasio itself: its Deployment, its telemetry receiver's Service, the
3
- * namespaces its workspaces run in, its identity in them, and the templates it makes
4
- * its workspaces' Sandboxes from (src/kube/config.ts); main's, or a branch environment's
5
- * in its own namespaces (./branch.ts), and main's Service of the forks it makes for them.
6
- */
7
- import { isDeepStrictEqual } from "node:util";
8
- import { Result } from "effect";
9
- import { BRANCH_FORK_PORT, branchSessionsNamespace } from "../../../src/branch/names.js";
10
- import { decodeKubeTemplates } from "../../../src/kube/config.js";
11
- import { BAYMA_STOP_SECONDS, CPU_FEATURES_ANNOTATION, STOP_WAIT_ARG } from "../../../src/kube/sandbox-shape.js";
12
- import { gvisorRuntimeClass, NODE_AGENT_PORT, NODE_AGENT_TOKEN_VARIABLE } from "../../../src/node-agent/names.js";
1
+ import { BRANCH_FORK_PORT, BRANCH_PARENT_LABEL, branchSessionsNamespace, MAIN_ENVIRONMENT_NAME } from "../../../src/branch/names.js";
2
+ import { ALASIO_GATEWAY_CALLER_KEY_VARIABLE, ALASIO_GATEWAY_ENVIRONMENT_VARIABLE, ALASIO_GATEWAY_FOLDERS_VARIABLE, ALASIO_GATEWAY_URL_VARIABLE } from "../../../src/gateway/names.js";
13
3
  import { gvisorReleases, NODE_PINS } from "../cluster/node.js";
14
4
  import { imageReference } from "../images.js";
15
- import { alasioAccount, busyLabelPolicy, cloneJobPolicy, resumedConditionPolicy, sandboxPolicy, sandboxPolicyName, secretPolicy } from "./admission.js";
5
+ import { busyLabelPolicy } from "./admission.js";
6
+ import { foldersRun } from "./folders.js";
7
+ import { carriedPools, GATEWAY, GATEWAY_PORT, sandboxServiceRuns } from "./sandbox-gateway.js";
16
8
  import { alasioGroup, environmentLogSecret, environmentName, lakeWakeTopic, LOG_BROKERS, telegramTopic } from "./log.js";
17
- import { BRANCH_FORK_SECRET, BRANCH_LABEL, componentName, databaseSecret, envOf, given, goJson, helperResources, hostVolumeMounts, hostVolumes, imagePullSecrets, labels, lakeRuns, MAIN_ENVIRONMENT, NAMESPACE, nodeAgentRuns, ONE, otelEnv, RELEASE, replicasOf, restrictedContainer, restrictedPod, roleVerifiers, selectorLabels, sha256, } from "./common.js";
9
+ import { BRANCH_FORK_SECRET, BRANCH_LABEL, componentName, databaseSecret, envOf, given, hostVolumeMounts, hostVolumes, imagePullSecrets, labels, lakeRuns, MAIN_ENVIRONMENT, NAMESPACE, ONE, otelEnv, RELEASE, replicasOf, restrictedContainer, restrictedPod, roleVerifiers, selectorLabels, SESSIONS_CALLER_SECRET, } from "./common.js";
18
10
  import { durationSeconds } from "./config.js";
19
11
  import { busyBudget } from "./disruption.js";
20
12
  import { priority } from "./priorities.js";
21
13
  import { verticalPodAutoscaler } from "./sizing.js";
22
- import { cloneJob } from "./workspace-storage.js";
23
- /** The port every workspace's bayma serves MCP on. */
24
- const BAYMA_PORT = 7290;
25
14
  /** The namespace of the sessions of the alasio of `environment`: the configured one, or a branch environment's own. */
26
15
  export function sessionsNamespace(config, { branch }) {
27
16
  return branch ? branchSessionsNamespace(branch) : config.sessions.namespace;
28
17
  }
29
- /** The namespaces whose Sandboxes alasio drives: the sessions', and the host profile's. */
30
- function workspaceNamespaces(config, environment) {
31
- return [...(config.sessions.enabled ? [sessionsNamespace(config, environment)] : []), ...(config.host.enabled ? [config.host.namespace] : [])];
32
- }
33
- /**
34
- * The template of session filesystems' Sandboxes: an empty, isolated workspace under
35
- * the sessions' runtime, restricted, on a volume of workspace storage's class when it is
36
- * on. alasio adds what is per Sandbox: its name and labels, bayma's token, and its
37
- * network mode. A branch environment's are in its own namespace, and neither clones a
38
- * session (its parent does, src/branch/fork.ts) nor removes JuiceFS's mount pods, which
39
- * are main's to drive.
40
- */
41
- function sessionsProfile(config, environment, { cpuBaseline, gvisorReleases }) {
42
- const { sessions, workspaceStorage } = config;
43
- const main = environment.branch === null;
44
- // Snapshotted once every node's CPU features are known, its sandboxes held to those all have.
45
- const snapshotted = main && nodeAgentRuns(config) && cpuBaseline !== null;
46
- const agent = imageReference(config.images.agent);
47
- const storageClassName = sessions.storage.storageClassName || (workspaceStorage.enabled ? workspaceStorage.storageClassName : "");
48
- // On workspace storage, the volume's mounts follow its mount pod's, as upstream's
49
- // template has them (gVisor does not, and alasio restarts a session whose mount is
50
- // gone), and its files are given the pod's group only when its root is not the group's,
51
- // as walking a JuiceFS volume is slow.
52
- const propagation = workspaceStorage.enabled ? { mountPropagation: "HostToContainer" } : {};
53
- const ownership = workspaceStorage.enabled ? { fsGroupChangePolicy: "OnRootMismatch" } : {};
54
- return {
55
- namespace: sessionsNamespace(config, environment),
56
- port: BAYMA_PORT,
57
- workspaceDir: "/workspace",
58
- egressGate: sessions.egressGate,
59
- fullModeNameservers: [...sessions.fullModeNameservers],
60
- ...(main && mountPodsRemoved(config) ? { mountPodNamespace: workspaceStorage.csi.namespace } : {}),
61
- podTemplate: {
62
- metadata: {
63
- labels: { "app.kubernetes.io/part-of": "alasio", "app.kubernetes.io/instance": RELEASE, "app.kubernetes.io/component": "session" },
64
- // Under gVisor, /tmp is a tmpfs of the sandbox's own, by gVisor's mount hints, rather
65
- // than the emptyDir's directory on the node: a snapshot of the sandbox holds it, as it
66
- // holds the files that bayma's runtimes keep open there.
67
- annotations: {
68
- "dev.gvisor.spec.mount.tmp.share": "container",
69
- "dev.gvisor.spec.mount.tmp.type": "tmpfs",
70
- "dev.gvisor.spec.mount.tmp.options": "rw",
71
- ...(snapshotted ? { [CPU_FEATURES_ANNOTATION]: cpuBaseline.join(",") } : {}),
72
- },
73
- },
74
- spec: {
75
- ...(sessions.runtimeClassName ? { runtimeClassName: sessions.runtimeClassName } : {}),
76
- ...priority("turn"),
77
- // A pod that ends is ended: alasio brings the session up in a new one, and nothing
78
- // starts bayma again in a sandbox that a snapshot has just ended.
79
- restartPolicy: "Never",
80
- // Ended by other than alasio, bayma holds its stop for alasio's snapshot, then stops.
81
- ...(snapshotted ? { terminationGracePeriodSeconds: stopWaitSeconds(config) + BAYMA_STOP_SECONDS } : {}),
82
- securityContext: { ...restrictedPod(1000, 1000), ...ownership },
83
- ...imagePullSecrets(config),
84
- ...given("nodeSelector", { ...sessions.nodeSelector }),
85
- ...given("tolerations", [...sessions.tolerations]),
86
- initContainers: [
87
- // The workspace and home on the session's volume, and bayma's toolbelt, which the
88
- // image installs, linked into the home.
89
- {
90
- name: "prepare",
91
- image: agent,
92
- imagePullPolicy: config.images.pullPolicy,
93
- command: [
94
- "/bin/sh",
95
- "-c",
96
- "mkdir -p /data/workspace /data/home/.local/share/bayma && ln -sfn /opt/bayma-data/bayma/toolbelt /data/home/.local/share/bayma/toolbelt",
97
- ],
98
- securityContext: restrictedContainer(),
99
- ...helperResources(),
100
- volumeMounts: [{ name: "data", mountPath: "/data" }],
101
- },
102
- ],
103
- containers: [{
104
- name: "bayma",
105
- image: agent,
106
- imagePullPolicy: config.images.pullPolicy,
107
- command: ["/usr/bin/tini", "-s", "--", "node", "/opt/bayma/bayma.js"],
108
- args: [
109
- "mcp-http",
110
- "--host",
111
- "0.0.0.0",
112
- "--port",
113
- String(BAYMA_PORT),
114
- "--state-dir",
115
- "/home/agent/.bayma",
116
- ...(snapshotted ? [STOP_WAIT_ARG, String(stopWaitSeconds(config) * 1000)] : []),
117
- ],
118
- workingDir: "/workspace",
119
- env: [
120
- { name: "HOME", value: "/home/agent" },
121
- { name: "USER", value: "agent" },
122
- { name: "BAYMA_PAYLOAD_DIR", value: "/opt/bayma/payload" },
123
- ],
124
- ports: [{ name: "mcp", containerPort: BAYMA_PORT }],
125
- securityContext: restrictedContainer(),
126
- resources: sessions.resources,
127
- volumeMounts: [
128
- { name: "data", mountPath: "/workspace", subPath: "workspace", ...propagation },
129
- { name: "data", mountPath: "/home/agent", subPath: "home", ...propagation },
130
- { name: "tmp", mountPath: "/tmp" },
131
- ],
132
- }],
133
- volumes: [{ name: "tmp", emptyDir: { sizeLimit: "2Gi" } }],
134
- },
135
- },
136
- volumeClaimTemplates: [{
137
- metadata: { name: "data" },
138
- spec: {
139
- accessModes: ["ReadWriteOnce"],
140
- ...(storageClassName ? { storageClassName } : {}),
141
- resources: { requests: { storage: sessions.storage.size } },
142
- },
143
- }],
144
- ...(main && sessionsForked(config) ? { clone: { claimTemplate: "data", job: cloneJob(config) } } : {}),
145
- ...(snapshotted ? { snapshots: sessionSnapshots(config, gvisorReleases) } : {}),
146
- };
147
- }
148
18
  /** How long a session's bayma holds a stop alasio did not make (sessions.snapshots.stopWait). */
149
- function stopWaitSeconds(config) {
19
+ export function stopWaitSeconds(config) {
150
20
  // A duration, as the configuration checks it.
151
21
  return durationSeconds(config.sessions.snapshots.stopWait) ?? 0;
152
22
  }
153
- /**
154
- * How session sandboxes are snapshotted as they are released (src/sandbox/snapshots.ts),
155
- * where the node agents run: once sessions.snapshots.releaseAfter has passed without an
156
- * exec or a turn, live every sessions.snapshots.liveEvery while execs run, and as their
157
- * pods are ended by other than alasio, within sessions.snapshots.stopWait, under the
158
- * gVisor `releases` alasio keeps (../kept-releases.ts), the
159
- * current one first, each restored with a RuntimeClass of its own, by the agents in
160
- * alasio's namespace.
161
- */
162
- function sessionSnapshots(config, releases) {
163
- return {
164
- // A duration, as the configuration checks it.
165
- releaseAfterSeconds: durationSeconds(config.sessions.snapshots.releaseAfter) ?? 0,
166
- liveEverySeconds: durationSeconds(config.sessions.snapshots.liveEvery) ?? 0,
167
- releases: releases.map((release) => ({ release, runtimeClassName: gvisorRuntimeClass(release) })),
168
- agents: {
169
- namespace: NAMESPACE,
170
- selector: Object.entries(selectorLabels("node-agent")).map(([label, value]) => `${label}=${value}`).join(","),
171
- port: NODE_AGENT_PORT,
172
- },
173
- };
174
- }
175
- /** Whether alasio forks sessions: where their volumes are workspace storage's, which JuiceFS clones. */
176
- export function sessionsForked({ sessions, workspaceStorage }) {
177
- return workspaceStorage.enabled && [workspaceStorage.storageClassName, ""].includes(sessions.storage.storageClassName);
178
- }
179
- /** The template of folder workspaces' bayma: bayma itself, as the operator, with the machine's mounts. */
180
- function hostProfile(config) {
181
- const { host } = config;
182
- return {
183
- namespace: host.namespace,
184
- port: BAYMA_PORT,
185
- stateRoot: host.stateRoot,
186
- podTemplate: {
187
- metadata: { labels: { "app.kubernetes.io/part-of": "alasio", "app.kubernetes.io/instance": RELEASE, "app.kubernetes.io/component": "folder-bayma" } },
188
- spec: {
189
- // May restart alasio, by rolling out its Deployment, as an agent restarts it.
190
- serviceAccountName: componentName("host-agent"),
191
- ...priority("turn"),
192
- securityContext: {
193
- runAsUser: host.uid,
194
- runAsGroup: host.gid,
195
- // The group the bayma token's Secret volume is given, which the operator's user
196
- // reads it as; the node's own paths the mounts give are left as they are.
197
- fsGroup: host.gid,
198
- ...given("supplementalGroups", [...host.supplementalGroups]),
199
- },
200
- ...imagePullSecrets(config),
201
- containers: [{
202
- name: "bayma",
203
- image: imageReference(host.baymaImage),
204
- imagePullPolicy: "IfNotPresent",
205
- args: ["mcp-http", "--host", "0.0.0.0", "--port", String(BAYMA_PORT), "--default-durability", "checkpointed"],
206
- workingDir: host.home,
207
- env: [{ name: "HOME", value: host.home }, ...envOf(host.env)],
208
- ports: [{ name: "mcp", containerPort: BAYMA_PORT }],
209
- securityContext: {
210
- // What bayma needs to snapshot its REPL sessions' processes as it stops and
211
- // restore them whole as it starts again.
212
- capabilities: { add: ["CHECKPOINT_RESTORE", "SYS_PTRACE"] },
213
- seccompProfile: { type: "Unconfined" },
214
- appArmorProfile: { type: "Unconfined" },
215
- },
216
- resources: host.resources,
217
- ...given("volumeMounts", hostVolumeMounts(config)),
218
- }],
219
- ...given("volumes", hostVolumes(config)),
220
- },
221
- },
222
- };
223
- }
224
- /** What a cluster keeps where up has found nothing of it yet: no baseline, the releases pinned, and no bot. */
225
- export const NOTHING_KEPT = { cpuBaseline: null, gvisorReleases: gvisorReleases(NODE_PINS), botChecksum: null };
23
+ /** What a cluster keeps where up has found nothing of it yet: no baseline, the releases pinned, no bot, no API server known, and none of the mount's Services. */
24
+ export const NOTHING_KEPT = { cpuBaseline: null, gvisorReleases: gvisorReleases(NODE_PINS), botChecksum: null, apiServer: null, mountServices: {} };
226
25
  /** Nothing carried forward: a first installation's, and a branch environment's. */
227
- export const NOTHING_CARRIED = { previousSessions: null, ...NOTHING_KEPT };
228
- /** The ConfigMap of the templates, by its name. */
229
- export const SANDBOX_TEMPLATES = componentName("sandbox-templates");
26
+ export const NOTHING_CARRIED = { previousPools: { none: null, full: null }, ...NOTHING_KEPT };
230
27
  /**
231
28
  * What the installation `config` describes, with what the cluster keeps, `kept`, carries
232
- * forward from `live`, the templates of the installation it replaces (its ConfigMap's
233
- * `templates.json`, if it has one): the sessions' profile it ran, where their template
234
- * changes, or else the one it carried. A profile is carried for one change of the
235
- * template, and no more. Pure, for tests.
236
- *
237
- * What it carries is pinned whole into the Sandbox policy (./admission.ts) as a template
238
- * alasio may make a Sandbox from, read back from the cluster rather than rendered from the
239
- * config: the ConfigMap is the anchor of that trust. It is one as long as only the
240
- * operator, by `alasio up`, writes it: alasio's ServiceAccount may write no ConfigMap
241
- * (./alasio.ts identity). Were alasio given that, it could write any pod template into
242
- * the ConfigMap, and the next `up` would let it make Sandboxes of it.
243
- */
244
- export function carriedFrom(config, live, kept) {
245
- const nothing = { ...NOTHING_CARRIED, ...kept };
246
- if (!config.sessions.enabled || live === undefined)
247
- return nothing;
248
- // Carried as the ConfigMap held them, which alasio read as they are: the profile a snapshot recorded the hash of.
249
- if (Result.isFailure(decodeKubeTemplates(SANDBOX_TEMPLATES, live)))
250
- return nothing;
251
- const held = JSON.parse(live);
252
- if (!held.sessions)
253
- return nothing;
254
- // The current profile as the ConfigMap holds it, to compare with what it held.
255
- const current = JSON.parse(goJson(sessionsProfile(config, MAIN_ENVIRONMENT, kept), ""));
256
- return { previousSessions: isDeepStrictEqual(held.sessions.podTemplate, current.podTemplate) ? (held.previousSessions ?? null) : held.sessions, ...kept };
257
- }
258
- /**
259
- * The CPU baseline the installation whose templates are `live` (as carriedFrom takes
260
- * them) holds sessions to: what its sessions' template sets, if anything.
29
+ * forward from `liveGateway`, the sandbox gateway's configuration of the installation it
30
+ * replaces (its ConfigMap's `config.json`, if it has one): its pools' templates before the
31
+ * current ones (./sandbox-gateway.ts carriedPools). Pure, for tests.
261
32
  */
262
- export function recordedCpuBaseline(live) {
263
- if (live === undefined || Result.isFailure(decodeKubeTemplates(SANDBOX_TEMPLATES, live)))
264
- return null;
265
- const held = JSON.parse(live);
266
- const recorded = held.sessions?.podTemplate.metadata?.annotations?.[CPU_FEATURES_ANNOTATION];
267
- return recorded ? recorded.split(",") : null;
268
- }
269
- /** The ConfigMap of the templates, and their checksum, which alasio's pod carries so it is replaced when they change. */
270
- function sandboxTemplates(config, environment, carried) {
271
- const name = SANDBOX_TEMPLATES;
272
- const templates = goJson({
273
- ...(config.sessions.enabled ? { sessions: sessionsProfile(config, environment, carried) } : {}),
274
- ...(config.sessions.enabled && carried.previousSessions ? { previousSessions: carried.previousSessions } : {}),
275
- ...(config.host.enabled ? { host: hostProfile(config) } : {}),
276
- }, " ");
277
- return {
278
- configMap: { apiVersion: "v1", kind: "ConfigMap", metadata: { name, namespace: environment.namespace, labels: labels("alasio") }, data: { "templates.json": templates } },
279
- checksum: sha256(templates),
280
- };
33
+ export function carriedFrom(config, kept, liveGateway) {
34
+ return { ...kept, previousPools: carriedPools(config, liveGateway, kept) };
281
35
  }
282
36
  /** Where alasio's Secrets of branch environments are mounted: main's key, or a branch's token. */
283
37
  const BRANCH_SECRETS = "/run/alasio/branch";
@@ -303,9 +57,9 @@ function branchSecretVolume(config, { branch }) {
303
57
  return [{ name: "branch", secret: { secretName: BRANCH_FORK_SECRET, items: [{ key: "token", path: "token" }] } }];
304
58
  return forksForBranches(config) ? [{ name: "branch", secret: { secretName: componentName("branches"), items: [{ key: "fork-key", path: "fork-key" }], optional: true } }] : [];
305
59
  }
306
- /** Whether main forks its sessions for branch environments: where it has Neon to branch, and forks sessions. */
60
+ /** Whether main forks its sessions for branch environments: where it has Neon to branch, and the sandbox service, whose node agents clone them. */
307
61
  export function forksForBranches(config) {
308
- return config.neon.enabled && sessionsForked(config);
62
+ return config.neon.enabled && sandboxServiceRuns(config);
309
63
  }
310
64
  /**
311
65
  * The roles main's alasio makes for what reads its Neon beside it (common.ts
@@ -319,7 +73,6 @@ function readerRoles(config, { branch }) {
319
73
  function alasioEnv(config, home, operatorHome, environment) {
320
74
  const { alasio, host } = config;
321
75
  return [
322
- { name: "ALASIO_KUBE_TEMPLATES", value: "/etc/alasio/templates.json" },
323
76
  { name: "ALASIO_STATE_DIR", value: "/var/lib/alasio/state" },
324
77
  { name: "HOME", value: home },
325
78
  // A home of alasio's own does not outlast its pod; the operator's keeps their login.
@@ -331,6 +84,7 @@ function alasioEnv(config, home, operatorHome, environment) {
331
84
  { name: "TELEGRAM_ALLOWED_USER_IDS", valueFrom: { secretKeyRef: { name: alasio.telegram.existingSecret, key: "allowedUserIds" } } },
332
85
  { name: "ALASIO_DATABASE_URL_FILE", value: "/run/alasio/database/url" },
333
86
  { name: "ALASIO_LAKE_VERIFIER_FILE", value: "/run/alasio/database/lake-verifier" },
87
+ { name: "ALASIO_HARNESS_VERIFIER_FILE", value: "/run/alasio/database/harness-verifier" },
334
88
  ...readerRoles(config, environment).map(({ key, variable }) => ({ name: variable, value: `/run/alasio/database/${key}` })),
335
89
  { name: "ALASIO_LAKE_ENABLED", value: config.lake.enabled && config.neon.enabled ? "1" : "0" },
336
90
  // Its Telegram updates: its environment's topic, read in its group as its principal.
@@ -341,17 +95,21 @@ function alasioEnv(config, home, operatorHome, environment) {
341
95
  { name: "ALASIO_TELEGRAM_GROUP", value: alasioGroup(environmentName(environment)) },
342
96
  // Its environment's wake topic, which it tells the lake's loader of each transcript it writes on, as that principal.
343
97
  ...(lakeRuns(config) ? [{ name: "ALASIO_LAKE_WAKE_TOPIC", value: lakeWakeTopic(environmentName(environment)) }] : []),
344
- { name: "ALASIO_TELEMETRY_RECEIVER_SERVICE", value: `${componentName("telemetry")}.${environment.namespace}.svc` },
345
- { name: "ALASIO_TELEMETRY_RECEIVER_PORT", value: String(config.telemetry.receiverPort) },
346
98
  ...(alasio.defaultHarness ? [{ name: "ALASIO_DEFAULT_HARNESS", value: alasio.defaultHarness }] : []),
347
99
  ...(host.enabled && host.workspaceRoot ? [{ name: "ALASIO_WORKSPACE_ROOT", value: host.workspaceRoot }] : []),
348
100
  ...(alasio.claude.existingSecret
349
101
  ? [{ name: "CLAUDE_CODE_OAUTH_TOKEN", valueFrom: { secretKeyRef: { name: alasio.claude.existingSecret, key: alasio.claude.key } } }]
350
102
  : []),
351
- // What it presents to the node agents, which snapshot its sessions.
352
- ...(environment.branch === null && nodeAgentRuns(config)
353
- ? [{ name: NODE_AGENT_TOKEN_VARIABLE, valueFrom: { secretKeyRef: { name: componentName("node-agent"), key: "token" } } }]
103
+ // Its environment's sessions, through the sandbox gateway, by its environment's own key of the gateway's.
104
+ ...(sandboxServiceRuns(config)
105
+ ? [
106
+ { name: ALASIO_GATEWAY_URL_VARIABLE, value: `http://${GATEWAY}.${NAMESPACE}.svc.cluster.local:${GATEWAY_PORT}` },
107
+ { name: ALASIO_GATEWAY_ENVIRONMENT_VARIABLE, value: environmentName(environment) },
108
+ { name: ALASIO_GATEWAY_CALLER_KEY_VARIABLE, valueFrom: { secretKeyRef: { name: SESSIONS_CALLER_SECRET, key: "caller-key" } } },
109
+ ]
354
110
  : []),
111
+ // Main's folder workspaces, the gateway's folder instances, where the host profile renders them.
112
+ ...(environment.branch === null && foldersRun(config) ? [{ name: ALASIO_GATEWAY_FOLDERS_VARIABLE, value: "1" }] : []),
355
113
  ...branchEnv(config, environment),
356
114
  ...otelEnv(config, environment),
357
115
  ...envOf(alasio.env),
@@ -367,10 +125,10 @@ function alasioEnv(config, home, operatorHome, environment) {
367
125
  * branch environment's runs in its namespace on the branch's database, told it is the
368
126
  * branch, with its token to ask main to fork the sessions it inherited; main's is given
369
127
  * the key branch environments' tokens are signed with, where it forks sessions. Its pod
370
- * carries the checksums of its templates and, where up read it, of its bot's Secret, so
371
- * it is replaced when either changes.
128
+ * carries, where up read it, the checksum of its bot's Secret, so it is replaced when it
129
+ * changes.
372
130
  */
373
- function deployment(config, templatesChecksum, botChecksum, environment) {
131
+ function deployment(config, botChecksum, environment) {
374
132
  const { alasio, host } = config;
375
133
  const operatorHome = host.enabled && host.alasioHome;
376
134
  const home = operatorHome ? host.home : "/var/lib/alasio/home";
@@ -390,7 +148,7 @@ function deployment(config, templatesChecksum, botChecksum, environment) {
390
148
  template: {
391
149
  metadata: {
392
150
  labels: labels("alasio"),
393
- annotations: { "checksum/sandbox-templates": templatesChecksum, ...(botChecksum === null ? {} : { "checksum/telegram": botChecksum }) },
151
+ ...(botChecksum === null ? {} : { annotations: { "checksum/telegram": botChecksum } }),
394
152
  },
395
153
  spec: {
396
154
  serviceAccountName: RELEASE,
@@ -406,16 +164,12 @@ function deployment(config, templatesChecksum, botChecksum, environment) {
406
164
  workingDir: home,
407
165
  env: alasioEnv(config, home, operatorHome, environment),
408
166
  ...given("envFrom", [...alasio.envFrom]),
409
- ports: [
410
- { name: "telemetry", containerPort: config.telemetry.receiverPort },
411
- ...(environment.branch === null && forksForBranches(config) ? [{ name: "branch-forks", containerPort: BRANCH_FORK_PORT }] : []),
412
- ],
167
+ ...(environment.branch === null && forksForBranches(config) ? { ports: [{ name: "branch-forks", containerPort: BRANCH_FORK_PORT }] } : {}),
413
168
  securityContext: restrictedContainer(),
414
169
  resources: alasio.resources,
415
170
  volumeMounts: [
416
171
  { name: "state", mountPath: "/var/lib/alasio/state" },
417
172
  ...(operatorHome ? [] : [{ name: "alasio-home", mountPath: home }]),
418
- { name: "templates", mountPath: "/etc/alasio", readOnly: true },
419
173
  { name: "database", mountPath: "/run/alasio/database", readOnly: true },
420
174
  { name: "log", mountPath: "/run/alasio/log", readOnly: true },
421
175
  ...branchSecret.map(({ name }) => ({ name, mountPath: BRANCH_SECRETS, readOnly: true })),
@@ -425,12 +179,11 @@ function deployment(config, templatesChecksum, botChecksum, environment) {
425
179
  volumes: [
426
180
  { name: "state", emptyDir: {} },
427
181
  ...(operatorHome ? [] : [{ name: "alasio-home", emptyDir: {} }]),
428
- { name: "templates", configMap: { name: componentName("sandbox-templates") } },
429
182
  {
430
183
  name: "database",
431
184
  secret: {
432
185
  secretName: databaseSecret(config),
433
- items: ["url", "lake-verifier", ...readerRoles(config, environment).map(({ key }) => key)].map((key) => ({ key, path: key })),
186
+ items: ["url", "lake-verifier", "harness-verifier", ...readerRoles(config, environment).map(({ key }) => key)].map((key) => ({ key, path: key })),
434
187
  },
435
188
  },
436
189
  { name: "log", secret: { secretName: environmentLogSecret(environment), items: [{ key: "password", path: "password" }] } },
@@ -445,15 +198,6 @@ function deployment(config, templatesChecksum, botChecksum, environment) {
445
198
  },
446
199
  };
447
200
  }
448
- /** Where session sandboxes export their telemetry: alasio's receiver. */
449
- function telemetryService(config, environment) {
450
- return {
451
- apiVersion: "v1",
452
- kind: "Service",
453
- metadata: { name: componentName("telemetry"), namespace: environment.namespace, labels: labels("alasio") },
454
- spec: { selector: selectorLabels("alasio"), ports: [{ name: "otlp-http", port: config.telemetry.receiverPort, targetPort: "telemetry" }] },
455
- };
456
- }
457
201
  /** Where branch environments ask main to fork the sessions they inherited (src/branch/fork.ts). */
458
202
  function branchForksService() {
459
203
  return {
@@ -466,8 +210,13 @@ function branchForksService() {
466
210
  /**
467
211
  * The namespaces workspaces run in, which alasio makes unless told not to. Sessions'
468
212
  * enforces Pod Security "restricted": nothing privileged, no host paths, no added
469
- * capabilities. The host profile's is "privileged", as its pods mount the machine. A
470
- * branch environment always has its own, beside its own namespace.
213
+ * capabilities; but where the sandbox service runs, whose pools' pods mount their own
214
+ * directories on their nodes, a host path no level but "privileged" allows, it enforces that
215
+ * and warns and audits of "restricted", the sandbox pods' policy holding every pod of it to the
216
+ * pools' templates instead (./admission.ts sandboxPodPolicy). The host profile's is
217
+ * "privileged", as its pods mount the machine. A branch environment always has its own,
218
+ * beside its own namespace, which records the environment it was branched from, main's
219
+ * (BRANCH_PARENT_LABEL), as the sandbox gateway forks main's sessions into it for that alone.
471
220
  */
472
221
  function namespaces(config, environment) {
473
222
  const { sessions, host } = config;
@@ -484,9 +233,12 @@ function namespaces(config, environment) {
484
233
  labels: {
485
234
  ...labels("session"),
486
235
  ...branched,
487
- "pod-security.kubernetes.io/enforce": "restricted",
488
- "pod-security.kubernetes.io/enforce-version": "latest",
236
+ ...(branch ? { [BRANCH_PARENT_LABEL]: MAIN_ENVIRONMENT_NAME } : {}),
237
+ ...(sandboxServiceRuns(config)
238
+ ? { "pod-security.kubernetes.io/enforce": "privileged", "pod-security.kubernetes.io/audit": "restricted", "pod-security.kubernetes.io/audit-version": "latest" }
239
+ : { "pod-security.kubernetes.io/enforce": "restricted", "pod-security.kubernetes.io/enforce-version": "latest" }),
489
240
  "pod-security.kubernetes.io/warn": "restricted",
241
+ "pod-security.kubernetes.io/warn-version": "latest",
490
242
  },
491
243
  },
492
244
  }]
@@ -501,97 +253,22 @@ function namespaces(config, environment) {
501
253
  ];
502
254
  }
503
255
  /**
504
- * Whether alasio removes the JuiceFS mount pods of a session whose mount is lost as it
505
- * restarts it (src/sandbox/index.ts): where it installs JuiceFS's driver, whose mount pods
506
- * it then knows to be in the driver's namespace.
507
- */
508
- function mountPodsRemoved({ workspaceStorage }) {
509
- return workspaceStorage.enabled && workspaceStorage.csi.enabled;
510
- }
511
- /**
512
- * alasio's identity: it drives its workspaces' Sandboxes, their token Secrets, and reads
513
- * files from sessions through exec, in the namespaces it owns, labelling their pods busy
514
- * while turns run in them, as it does its own in its namespace (src/kube/busy.ts); where
515
- * it snapshots sessions, sets a restored session's readiness; where it forks
516
- * sessions, reads sessions' volume claims and runs the Jobs that clone them in its own;
517
- * where the node agents run, watches pods in its own, theirs among them, and reads the
518
- * cluster's nodes, for a restore to go to one that takes its pod; and, where it installs
519
- * JuiceFS's driver, lists and deletes pods in the driver's namespace, its mount pods; and
520
- * nothing else.
256
+ * alasio's identity: it labels its own pod busy while turns run in it (src/kube/busy.ts), and
257
+ * nothing else. Its sessions and its folder workspaces are the sandbox gateway's, which it
258
+ * reaches by its key alone.
521
259
  */
522
260
  function identity(config, environment) {
523
- const main = environment.branch === null;
524
- const binding = (metadata) => ({
261
+ const inOwnNamespace = { name: RELEASE, namespace: environment.namespace, labels: labels("alasio") };
262
+ const own = { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: inOwnNamespace, rules: [{ apiGroups: [""], resources: ["pods"], verbs: ["list", "patch"] }] };
263
+ const binding = {
525
264
  apiVersion: "rbac.authorization.k8s.io/v1",
526
265
  kind: "RoleBinding",
527
- metadata,
266
+ metadata: inOwnNamespace,
528
267
  subjects: [{ kind: "ServiceAccount", name: RELEASE, namespace: environment.namespace }],
529
268
  roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: RELEASE },
530
- });
531
- const inDriverNamespace = { name: RELEASE, namespace: config.workspaceStorage.csi.namespace, labels: labels("alasio") };
532
- const mountPods = {
533
- apiVersion: "rbac.authorization.k8s.io/v1",
534
- kind: "Role",
535
- metadata: inDriverNamespace,
536
- rules: [{ apiGroups: [""], resources: ["pods"], verbs: ["list", "delete"] }],
537
- };
538
- const inOwnNamespace = { name: RELEASE, namespace: environment.namespace, labels: labels("alasio") };
539
- const own = {
540
- apiVersion: "rbac.authorization.k8s.io/v1",
541
- kind: "Role",
542
- metadata: inOwnNamespace,
543
- rules: [
544
- // Its own pod's busy label; and each node's agent, found by its pod on the node.
545
- { apiGroups: [""], resources: ["pods"], verbs: main && nodeAgentRuns(config) ? ["list", "watch", "patch"] : ["list", "patch"] },
546
- ...(main && sessionsForked(config) ? [{ apiGroups: ["batch"], resources: ["jobs"], verbs: ["create", "get", "delete"] }] : []),
547
- ],
548
269
  };
549
270
  const serviceAccount = { apiVersion: "v1", kind: "ServiceAccount", metadata: { name: RELEASE, namespace: environment.namespace, labels: labels("alasio") } };
550
- // The nodes, read alone, for a restore to go to one that takes the session's pod (src/sandbox/snapshots.ts).
551
- const nodes = { name: componentName("nodes"), labels: labels("alasio") };
552
- const nodesRole = {
553
- apiVersion: "rbac.authorization.k8s.io/v1",
554
- kind: "ClusterRole",
555
- metadata: nodes,
556
- rules: [{ apiGroups: [""], resources: ["nodes"], verbs: ["get"] }],
557
- };
558
- const nodesBinding = {
559
- apiVersion: "rbac.authorization.k8s.io/v1",
560
- kind: "ClusterRoleBinding",
561
- metadata: nodes,
562
- subjects: [{ kind: "ServiceAccount", name: RELEASE, namespace: environment.namespace }],
563
- roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "ClusterRole", name: nodes.name },
564
- };
565
- return [
566
- serviceAccount,
567
- ...workspaceNamespaces(config, environment).flatMap((namespace) => {
568
- const metadata = { name: RELEASE, namespace, labels: labels("alasio") };
569
- const forked = main && sessionsForked(config) && namespace === config.sessions.namespace;
570
- const snapshotted = main && nodeAgentRuns(config) && namespace === config.sessions.namespace;
571
- const role = {
572
- apiVersion: "rbac.authorization.k8s.io/v1",
573
- kind: "Role",
574
- metadata,
575
- rules: [
576
- { apiGroups: ["agents.x-k8s.io"], resources: ["sandboxes"], verbs: ["get", "list", "watch", "create", "patch", "delete"] },
577
- { apiGroups: [""], resources: ["secrets"], verbs: ["get", "create"] },
578
- { apiGroups: [""], resources: ["pods"], verbs: ["get", "list", "patch"] },
579
- { apiGroups: [""], resources: ["pods/exec"], verbs: ["create", "get"] },
580
- ...(forked ? [{ apiGroups: [""], resources: ["persistentvolumeclaims"], verbs: ["get"] }] : []),
581
- // A restored session's readiness, once its sandbox is let run, and the network policies that must hold it first (src/sandbox/snapshots.ts).
582
- ...(snapshotted
583
- ? [{ apiGroups: [""], resources: ["pods/status"], verbs: ["patch"] }, { apiGroups: ["networking.k8s.io"], resources: ["networkpolicies"], verbs: ["list"] }]
584
- : []),
585
- ],
586
- };
587
- return [role, binding(metadata)];
588
- }),
589
- own,
590
- binding(inOwnNamespace),
591
- ...(main && mountPodsRemoved(config) ? [mountPods, binding(inDriverNamespace)] : []),
592
- ...(main && nodeAgentRuns(config) ? [nodesRole, nodesBinding] : []),
593
- ...(config.host.enabled ? hostAgentIdentity(config) : []),
594
- ];
271
+ return [serviceAccount, own, binding, ...(environment.branch === null && foldersRun(config) ? hostAgentIdentity(config) : [])];
595
272
  }
596
273
  /**
597
274
  * Folder workspaces' bayma: the operator's agents, which may restart alasio as they
@@ -626,31 +303,18 @@ function hostAgentIdentity(config) {
626
303
  }
627
304
  /** The objects of the alasio of `environment`, main's unless a branch environment's, with what main's carries forward. */
628
305
  export function alasioObjects(config, environment = MAIN_ENVIRONMENT, carried = NOTHING_CARRIED) {
629
- const templates = sandboxTemplates(config, environment, carried);
630
- const alasio = deployment(config, templates.checksum, carried.botChecksum, environment);
306
+ const alasio = deployment(config, carried.botChecksum, environment);
631
307
  return [
632
308
  ...namespaces(config, environment),
633
309
  ...identity(config, environment),
634
- templates.configMap,
635
310
  alasio,
636
311
  // It runs turns, in its harnesses, which a recreation would end.
637
312
  verticalPodAutoscaler(alasio, "Recommended"),
638
- // A drain waits for the turns running in its pod and its workspaces' to end.
639
- ...[environment.namespace, ...workspaceNamespaces(config, environment)].map(busyBudget),
640
- telemetryService(config, environment),
313
+ // A drain waits for the turns running in its pod to end.
314
+ busyBudget(environment.namespace),
641
315
  ...(environment.branch === null && forksForBranches(config) ? [branchForksService()] : []),
642
- // What alasio may do beyond what RBAC can say; the cluster's, but for its Sandboxes', made with main's.
643
- ...(environment.branch === null ? [...busyLabelPolicy(), ...secretPolicy()] : []),
644
- // Where sessions are snapshotted, what alasio may set of a restored session's pod's status.
645
- ...(environment.branch === null && config.sessions.enabled && nodeAgentRuns(config)
646
- ? resumedConditionPolicy(componentName("resumed-condition"), alasioAccount(environment.namespace), config.sessions.namespace)
647
- : []),
648
- ...(environment.branch === null && sessionsForked(config) ? cloneJobPolicy(cloneJob(config)) : []),
649
- ...sandboxPolicy(sandboxPolicyName(environment.namespace), alasioAccount(environment.namespace), {
650
- sessions: config.sessions.enabled ? sessionsProfile(config, environment, carried) : null,
651
- previousSessions: config.sessions.enabled ? carried.previousSessions : null,
652
- host: config.host.enabled ? hostProfile(config) : null,
653
- }),
316
+ // What alasio may do beyond what RBAC can say: the cluster's, made with main's.
317
+ ...(environment.branch === null ? busyLabelPolicy() : []),
654
318
  ];
655
319
  }
656
320
  //# sourceMappingURL=alasio.js.map