@yourrealm/sdk 0.0.0-alpha.1378.ce03c8dc

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 (230) hide show
  1. package/LICENSE-EXCEPTION +26 -0
  2. package/README.md +34 -0
  3. package/dist/chunk-3R6KEFOI.js +22 -0
  4. package/dist/chunk-67BDAMYX.js +376 -0
  5. package/dist/chunk-AITGS27R.js +139 -0
  6. package/dist/chunk-CMAEHQ4F.js +6 -0
  7. package/dist/chunk-MLKGABMK.js +9 -0
  8. package/dist/chunk-O3UIK34B.js +1430 -0
  9. package/dist/chunk-VHTUSZUX.js +176 -0
  10. package/dist/cli.js +9006 -0
  11. package/dist/docker.js +54 -0
  12. package/dist/index.js +1053 -0
  13. package/dist/ingress.js +436 -0
  14. package/dist/jsx-runtime.js +2 -0
  15. package/dist/preact.js +22 -0
  16. package/dist/service-url.js +11 -0
  17. package/dist/services.js +213 -0
  18. package/dist/test.js +5069 -0
  19. package/dist/ui.js +254 -0
  20. package/dist/worker-protocol.js +89 -0
  21. package/package.json +112 -0
  22. package/src/app-icon.ts +74 -0
  23. package/src/apply.ts +52 -0
  24. package/src/bundle-ui.ts +211 -0
  25. package/src/bundle.ts +546 -0
  26. package/src/cli/build-dev-landing.ts +84 -0
  27. package/src/cli/build-install-panel.ts +90 -0
  28. package/src/cli/build-runtime.ts +133 -0
  29. package/src/cli/build.ts +63 -0
  30. package/src/cli/dev.ts +107 -0
  31. package/src/cli/down.ts +60 -0
  32. package/src/cli/main.ts +56 -0
  33. package/src/cli/test.ts +61 -0
  34. package/src/cli/validate.ts +37 -0
  35. package/src/compile.ts +100 -0
  36. package/src/create-app.ts +982 -0
  37. package/src/deep-merge.ts +33 -0
  38. package/src/dev/fixture.ts +48 -0
  39. package/src/dev/install-handler.ts +383 -0
  40. package/src/dev/server.ts +968 -0
  41. package/src/dev/stack.ts +108 -0
  42. package/src/dev/state.ts +159 -0
  43. package/src/docker.ts +96 -0
  44. package/src/errors.ts +61 -0
  45. package/src/factory.ts +481 -0
  46. package/src/garage.ts +56 -0
  47. package/src/generator/hash.ts +6 -0
  48. package/src/generator/resolve-refs.ts +207 -0
  49. package/src/grants-builder.ts +100 -0
  50. package/src/grants.ts +260 -0
  51. package/src/home-api.ts +172 -0
  52. package/src/home-cookie.ts +10 -0
  53. package/src/home-registry.ts +120 -0
  54. package/src/home.ts +187 -0
  55. package/src/host-registry.ts +143 -0
  56. package/src/http-header.ts +20 -0
  57. package/src/identifiers.ts +49 -0
  58. package/src/index.ts +66 -0
  59. package/src/info.ts +64 -0
  60. package/src/ini.ts +50 -0
  61. package/src/json.ts +14 -0
  62. package/src/jsx-runtime/index.ts +5 -0
  63. package/src/keys.ts +29 -0
  64. package/src/mysql.ts +56 -0
  65. package/src/pack-build.ts +108 -0
  66. package/src/pack.ts +115 -0
  67. package/src/peer-registry.ts +121 -0
  68. package/src/peer-world.ts +117 -0
  69. package/src/permission-meta.ts +116 -0
  70. package/src/permissions.ts +108 -0
  71. package/src/pkg-root.ts +34 -0
  72. package/src/postgres.ts +79 -0
  73. package/src/preact/index.ts +27 -0
  74. package/src/redis.ts +43 -0
  75. package/src/ref.ts +271 -0
  76. package/src/remote-registry.ts +33 -0
  77. package/src/required-host-kinds.ts +110 -0
  78. package/src/resolve-context.ts +111 -0
  79. package/src/resolve.ts +471 -0
  80. package/src/runtime/docker/compose/compose-schema.ts +118 -0
  81. package/src/runtime/docker/compose/convert-to-docker-compose.ts +278 -0
  82. package/src/runtime/docker/compose/default-hardening.ts +21 -0
  83. package/src/runtime/docker/compose/effects.ts +107 -0
  84. package/src/runtime/docker/compose/index.ts +17 -0
  85. package/src/runtime/docker/compose/labels.ts +39 -0
  86. package/src/runtime/docker/compose/types.ts +78 -0
  87. package/src/runtime/docker/home-proxy-server/index.ts +177 -0
  88. package/src/runtime/docker/home-proxy-server/protocol.ts +53 -0
  89. package/src/runtime/docker/ingress/build-config.ts +819 -0
  90. package/src/runtime/docker/ingress/index.ts +16 -0
  91. package/src/runtime/docker/ingress/types.ts +182 -0
  92. package/src/runtime/docker/log-frames.ts +121 -0
  93. package/src/runtime/docker/logs-server/index.ts +366 -0
  94. package/src/runtime/docker/spawn-server/context-key.ts +82 -0
  95. package/src/runtime/docker/spawn-server/index.ts +492 -0
  96. package/src/runtime/docker/spawn-server/protocol.ts +70 -0
  97. package/src/runtime/docker/spawn-server/spec.ts +122 -0
  98. package/src/runtime/docker/stats-server/disks.ts +161 -0
  99. package/src/runtime/docker/stats-server/index.ts +322 -0
  100. package/src/runtime/home-client/index.ts +71 -0
  101. package/src/runtime/log-line.ts +24 -0
  102. package/src/runtime/logs-client/index.ts +175 -0
  103. package/src/runtime/spawn-client/index.ts +93 -0
  104. package/src/runtime/spawn-client/kaniko.ts +79 -0
  105. package/src/runtime/stats-client/index.ts +65 -0
  106. package/src/runtime/stats-sample.ts +95 -0
  107. package/src/safe-json.ts +37 -0
  108. package/src/sandbox.ts +1209 -0
  109. package/src/schema/build-apps.ts +38 -0
  110. package/src/schema/home.ts +217 -0
  111. package/src/schema/host.ts +792 -0
  112. package/src/schema/inputs.ts +26 -0
  113. package/src/schema/nodes.ts +75 -0
  114. package/src/schema/peer.ts +400 -0
  115. package/src/schema/public-domain.ts +116 -0
  116. package/src/schema/remote.ts +74 -0
  117. package/src/security.ts +594 -0
  118. package/src/serialize.ts +21 -0
  119. package/src/service-definition.ts +284 -0
  120. package/src/service-url.ts +64 -0
  121. package/src/service.ts +39 -0
  122. package/src/services/index.ts +5 -0
  123. package/src/shared.ts +65 -0
  124. package/src/speculative-silo.ts +96 -0
  125. package/src/stable-secret.ts +14 -0
  126. package/src/surfaces.ts +264 -0
  127. package/src/tailwind-bin.ts +56 -0
  128. package/src/test/boot.ts +192 -0
  129. package/src/test/compile.ts +501 -0
  130. package/src/test/describe.ts +117 -0
  131. package/src/test/index.ts +11 -0
  132. package/src/test/ingress-fixture.ts +37 -0
  133. package/src/test/types.ts +318 -0
  134. package/src/trusted-headers.ts +79 -0
  135. package/src/types.ts +126 -0
  136. package/src/ui/components/aside.tsx +96 -0
  137. package/src/ui/components/badge.tsx +42 -0
  138. package/src/ui/components/button.tsx +59 -0
  139. package/src/ui/components/card.tsx +106 -0
  140. package/src/ui/components/checkbox.tsx +37 -0
  141. package/src/ui/components/code-block.tsx +29 -0
  142. package/src/ui/components/combobox.tsx +119 -0
  143. package/src/ui/components/config-field.tsx +284 -0
  144. package/src/ui/components/dialog.tsx +137 -0
  145. package/src/ui/components/domain-editor.tsx +317 -0
  146. package/src/ui/components/domain-list.ts +205 -0
  147. package/src/ui/components/dropdown-menu.tsx +73 -0
  148. package/src/ui/components/empty-state.tsx +51 -0
  149. package/src/ui/components/highlight.ts +59 -0
  150. package/src/ui/components/icons.tsx +101 -0
  151. package/src/ui/components/image-posture.tsx +85 -0
  152. package/src/ui/components/input.tsx +28 -0
  153. package/src/ui/components/label.tsx +16 -0
  154. package/src/ui/components/line-chart.tsx +113 -0
  155. package/src/ui/components/page.tsx +39 -0
  156. package/src/ui/components/permission-row.tsx +262 -0
  157. package/src/ui/components/permission-section.tsx +51 -0
  158. package/src/ui/components/pie-chart.tsx +245 -0
  159. package/src/ui/components/schema-block.tsx +60 -0
  160. package/src/ui/components/separator.tsx +42 -0
  161. package/src/ui/components/sheet.tsx +130 -0
  162. package/src/ui/components/sidebar.tsx +650 -0
  163. package/src/ui/components/silo-picker.tsx +510 -0
  164. package/src/ui/components/skeleton.tsx +13 -0
  165. package/src/ui/components/sonner.tsx +33 -0
  166. package/src/ui/components/switch.tsx +58 -0
  167. package/src/ui/components/table-view.ts +219 -0
  168. package/src/ui/components/table.tsx +506 -0
  169. package/src/ui/components/tabs.tsx +59 -0
  170. package/src/ui/components/timeago.tsx +84 -0
  171. package/src/ui/components/tooltip.tsx +56 -0
  172. package/src/ui/components.json +21 -0
  173. package/src/ui/dev/dev-shell.tsx +145 -0
  174. package/src/ui/home/home-grid.tsx +177 -0
  175. package/src/ui/hooks/use-mobile.ts +19 -0
  176. package/src/ui/index.ts +189 -0
  177. package/src/ui/install/adapter.ts +193 -0
  178. package/src/ui/install/deploy-targets-picker.tsx +185 -0
  179. package/src/ui/install/install-app-panel.tsx +912 -0
  180. package/src/ui/install/install-error.tsx +62 -0
  181. package/src/ui/install/perms-inactive.ts +50 -0
  182. package/src/ui/install/required-fields.ts +37 -0
  183. package/src/ui/install/tabs-form.tsx +557 -0
  184. package/src/ui/tokens.css +279 -0
  185. package/src/ui/utils.ts +78 -0
  186. package/src/ui-error.ts +139 -0
  187. package/src/ui-runtime/dev-landing-entry.tsx +258 -0
  188. package/src/ui-runtime/index.ts +83 -0
  189. package/src/ui-runtime/install-entry.tsx +119 -0
  190. package/src/ui-runtime/mount.ts +367 -0
  191. package/src/ui-runtime/runtime.css +56 -0
  192. package/src/ui-surface.ts +451 -0
  193. package/src/valkey.ts +43 -0
  194. package/src/worker/extract-tar.ts +125 -0
  195. package/src/worker/identity.ts +183 -0
  196. package/src/worker/index.ts +16 -0
  197. package/src/worker/public-ip.ts +92 -0
  198. package/src/worker/queue.ts +54 -0
  199. package/src/worker/reconcile.ts +127 -0
  200. package/src/worker/start-worker.ts +227 -0
  201. package/src/worker/ui-proxy.ts +335 -0
  202. package/src/worker/uplink.ts +523 -0
  203. package/src/worker/validate.ts +34 -0
  204. package/src/worker/verify-relay.ts +102 -0
  205. package/src/worker-protocol/app-dirs.ts +94 -0
  206. package/src/worker-protocol/home-proxy-paths.ts +76 -0
  207. package/src/worker-protocol/index.ts +55 -0
  208. package/src/worker-protocol/ingress.ts +166 -0
  209. package/src/worker-protocol/logs-socket-path.ts +50 -0
  210. package/src/worker-protocol/networks.ts +39 -0
  211. package/src/worker-protocol/node-version.ts +70 -0
  212. package/src/worker-protocol/silo-paths.ts +114 -0
  213. package/src/worker-protocol/spawn-paths.ts +165 -0
  214. package/src/worker-protocol/stats-socket-path.ts +40 -0
  215. package/src/worker-protocol/update-files.ts +77 -0
  216. package/src/worker-protocol/uplink/constants.ts +86 -0
  217. package/src/worker-protocol/uplink/errors.ts +61 -0
  218. package/src/worker-protocol/uplink/frame.ts +114 -0
  219. package/src/worker-protocol/uplink/identity.ts +40 -0
  220. package/src/worker-protocol/uplink/index.ts +63 -0
  221. package/src/worker-protocol/uplink/methods.ts +462 -0
  222. package/src/worker-protocol/uplink/mux.ts +646 -0
  223. package/src/worker-protocol/uplink/protocol.ts +11 -0
  224. package/src/worker-protocol/uplink/rpc.ts +309 -0
  225. package/src/worker-protocol/uplink/session.ts +540 -0
  226. package/src/worker-protocol/uplink/test-support.ts +188 -0
  227. package/src/worker-protocol/worker-mount.ts +27 -0
  228. package/src/worker-protocol/worker-state.ts +190 -0
  229. package/src/xml.ts +49 -0
  230. package/src/yaml.ts +15 -0
@@ -0,0 +1,26 @@
1
+ Realm SDK Licensing Exception
2
+
3
+ SPDX-License-Identifier: AGPL-3.0-only WITH Classpath-exception-2.0
4
+
5
+ The Realm SDK (@yourrealm/sdk) is licensed under the GNU Affero General
6
+ Public License, version 3 (see the LICENSE file at the repository root),
7
+ with the following exception. The exception text is the GNU Classpath
8
+ Exception 2.0, with its reference to the covering license retargeted to
9
+ the GNU Affero General Public License version 3:
10
+
11
+ Linking this library statically or dynamically with other modules is
12
+ making a combined work based on this library. Thus, the terms and
13
+ conditions of the GNU Affero General Public License version 3 cover the
14
+ whole combination.
15
+
16
+ As a special exception, the copyright holders of this library give you
17
+ permission to link this library with independent modules to produce an
18
+ executable, regardless of the license terms of these independent
19
+ modules, and to copy and distribute the resulting executable under
20
+ terms of your choice, provided that you also meet, for each linked
21
+ independent module, the terms and conditions of the license of that
22
+ module. An independent module is a module which is not derived from or
23
+ based on this library. If you modify this library, you may extend this
24
+ exception to your version of the library, but you are not obligated to
25
+ do so. If you do not wish to do so, delete this exception statement
26
+ from your version.
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # @yourrealm/sdk
2
+
3
+ The Realm SDK: the authoring surface for Realm apps (`createApp`, `r`, the
4
+ service classes), the `ui` surface, the `realm-sdk` CLI and the test harness.
5
+
6
+ ## Install
7
+
8
+ Every push to Realm's `main` publishes the SDK under the `alpha` dist-tag,
9
+ versioned by commit. Pin the tag and move it when you want to:
10
+
11
+ ```bash
12
+ pnpm add @yourrealm/sdk@alpha
13
+ pnpm update @yourrealm/sdk
14
+ ```
15
+
16
+ The runtime is Deno: `realm-sdk test` runs `Deno.test` underneath, and the
17
+ package ships its TypeScript source as the `types` condition, so `deno check`
18
+ sees the real signatures. Set `nodeModulesDir` to `"manual"` in `deno.json`
19
+ and let pnpm own `node_modules`.
20
+
21
+ ## License
22
+
23
+ `AGPL-3.0-only WITH Classpath-exception-2.0`.
24
+
25
+ Realm's platform (Home, Worker) is licensed under the GNU Affero General
26
+ Public License v3. This SDK adds a linking exception, based on the GNU
27
+ Classpath Exception 2.0, so that authoring a Realm app (`realm.ts`) that
28
+ imports this SDK does not, by itself, place your app under the AGPL. The
29
+ exception frees users of the SDK; it does not extend to modified versions
30
+ of the SDK itself, which remain AGPL.
31
+
32
+ See [LICENSE-EXCEPTION](./LICENSE-EXCEPTION) for the full text and the
33
+ repository's [LICENSE](https://github.com/benjick/holden/blob/main/LICENSE) for
34
+ the AGPL.
@@ -0,0 +1,22 @@
1
+ // src/service-url.ts
2
+ function structuredHost(appId, serviceId, slug, baseDomain) {
3
+ return `${appId}-${serviceId}.${slug}.${baseDomain}`;
4
+ }
5
+ function serviceUrl(args) {
6
+ if (!args.hasRouter) return null;
7
+ if (args.publicDomain) return `https://${args.publicDomain}`;
8
+ if (!args.slug || !args.baseDomain) return null;
9
+ return `https://${structuredHost(args.appId, args.serviceId, args.slug, args.baseDomain)}`;
10
+ }
11
+ function firstPublicDomain(publicDomain) {
12
+ if (publicDomain == null || typeof publicDomain !== "object") return void 0;
13
+ const hosts = publicDomain.hosts;
14
+ if (Array.isArray(hosts) && typeof hosts[0] === "string") return hosts[0];
15
+ return void 0;
16
+ }
17
+
18
+ export {
19
+ structuredHost,
20
+ serviceUrl,
21
+ firstPublicDomain
22
+ };
@@ -0,0 +1,376 @@
1
+ import {
2
+ SPECULATIVE_SILO,
3
+ hostKey,
4
+ nameSchema
5
+ } from "./chunk-VHTUSZUX.js";
6
+
7
+ // src/worker-protocol/worker-state.ts
8
+ import { z as z2 } from "zod";
9
+
10
+ // src/worker-protocol/ingress.ts
11
+ import { z } from "zod";
12
+ var acmeIssuerSchema = z.object({
13
+ serverUrl: z.string(),
14
+ username: z.string(),
15
+ apiKey: z.string(),
16
+ ca: z.string(),
17
+ caRootPem: z.string()
18
+ });
19
+ var ingressRouterSchema = z.object({
20
+ containerPort: z.number().int().min(1).max(65535),
21
+ /** Operator-granted hostnames for the additional ungated public route. */
22
+ publicDomain: z.union([z.string(), z.array(z.string())]).optional(),
23
+ /** With several granted hosts: serve them all, or serve one and 301 the rest. */
24
+ domainMode: z.enum(["redirect", "all"]),
25
+ pathPrefix: z.string().regex(/^\/[a-zA-Z0-9/_-]*$/, "pathPrefix must start with / and contain only [a-zA-Z0-9/_-]").optional(),
26
+ compress: z.boolean(),
27
+ maxRequestSizeMb: z.number().nullable(),
28
+ rateLimit: z.union([
29
+ z.number(),
30
+ z.object({
31
+ requests: z.number(),
32
+ window: z.string().regex(/^\d+(ms|s|m|h)$/, "window must be a duration like 10s, 1m, 1h")
33
+ })
34
+ ]).nullable(),
35
+ /** Response-header overrides; `null` deletes the renderer's default. */
36
+ headers: z.record(z.string(), z.string().nullable()).optional()
37
+ });
38
+ var ingressRoutedAppSchema = z.object({
39
+ appId: z.string(),
40
+ services: z.array(z.object({ serviceId: z.string(), router: ingressRouterSchema })),
41
+ /**
42
+ * Header-brokering names the app declared via `getTrustedHeaders`. The
43
+ * renderer injects these on the authenticated route (lifted off the
44
+ * forward-auth response) and strips them everywhere else, so identity can't
45
+ * be spoofed.
46
+ */
47
+ trustedHeaderKeys: z.array(z.string()).optional()
48
+ });
49
+ var ingressInputSchema = z.object({
50
+ /** Every routed service on this worker, ordered canonically by `appId`. */
51
+ routedApps: z.array(ingressRoutedAppSchema),
52
+ /**
53
+ * The Home dashboard's own host, `{homeSlug}.{workerId}.{baseDomain}` - one
54
+ * label under the worker wildcard, which a renderer has to match ahead of
55
+ * that wildcard so the not-routed holding page never shadows it.
56
+ *
57
+ * Null on a box that does not run Home. The name resolves realm-wide (it
58
+ * sits under the realm's wildcard), so emitting the route on a worker-only
59
+ * box would answer a hit there with a page claiming the control plane is
60
+ * down - and, under BYO public providers, cost a real per-host certificate
61
+ * on every worker. Home knows which box is which from `home_state
62
+ * .home_worker_id`.
63
+ */
64
+ dashboardHost: z.string().nullable(),
65
+ /**
66
+ * This worker's short slug: the `{workerId}` level of every app subdomain,
67
+ * and the label it owns under acme-dns.
68
+ */
69
+ workerId: z.string(),
70
+ /**
71
+ * The realm's domain base (`{slug}.yourrealm.me`, or the operator's own).
72
+ * With `workerId` it forms every structured authenticated subdomain. Empty
73
+ * on the placeholder identity, before an install is configured.
74
+ */
75
+ baseDomain: z.string(),
76
+ /** DNS-01 inputs for the worker wildcard, or null (dev / e2e / first boot). */
77
+ acmeDns: acmeIssuerSchema.extend({ baseDomain: z.string() }).nullable(),
78
+ /**
79
+ * BYO public-DNS providers, in issue order. `apiToken` is a plain string:
80
+ * a `Ref` cannot ride the wire, and by this point Home has resolved it.
81
+ */
82
+ publicProviders: z.array(
83
+ z.object({ name: z.enum(["cloudflare", "hetzner"]), apiToken: z.string() })
84
+ ),
85
+ /**
86
+ * CDNs in front of this realm, whose edge addresses ingress trusts when
87
+ * resolving the real client IP. The addresses themselves are not here - the
88
+ * renderer's runtime fetches and refreshes them.
89
+ */
90
+ cdnProviders: z.array(z.string()),
91
+ /**
92
+ * The signing-certificate fingerprints in the Digital Asset Links statement
93
+ * the ingress serves on every host - the statement's
94
+ * `sha256_cert_fingerprints`, which Chrome matches the companion APK's own
95
+ * signature against before it drops the Trusted Web Activity's toolbar.
96
+ *
97
+ * Operator-set rather than a constant, because which certificate signs the
98
+ * installed APK is a property of how that operator got it: Play App Signing
99
+ * re-signs with Google's key, direct distribution keeps the release
100
+ * keystore's, and a locally built debug APK carries a third.
101
+ *
102
+ * The shape is pinned here because the renderer inlines these strings into a
103
+ * JSON body Caddy's `ReplaceKnown` walks: uppercase hex bytes and colons can
104
+ * never form a `{placeholder}`, so a value that parses cannot reach into the
105
+ * replacer.
106
+ */
107
+ companionFingerprints: z.array(
108
+ z.string().regex(/^[0-9A-F]{2}(:[0-9A-F]{2}){31}$/, "expected 32 colon-separated uppercase hex bytes")
109
+ )
110
+ });
111
+ var PLATFORM_INGRESS_PORTS = [80, 443];
112
+
113
+ // src/worker-protocol/worker-state.ts
114
+ var dependsOnEdgeSchema = z2.object({
115
+ providerAppId: z2.string(),
116
+ providerServiceName: z2.string(),
117
+ condition: z2.enum(["started", "healthy"])
118
+ });
119
+ var duration = z2.number().int().nonnegative();
120
+ var containerPort = z2.number().int().min(1).max(65535);
121
+ var healthcheckSchema = z2.object({
122
+ test: z2.array(z2.string()),
123
+ interval: duration,
124
+ timeout: duration,
125
+ retries: z2.number().int().nonnegative(),
126
+ startPeriod: duration
127
+ });
128
+ var hostPortSchema = z2.object({
129
+ containerPort,
130
+ hostPort: z2.number().int().min(1).max(65535),
131
+ protocol: z2.enum(["tcp", "udp"]).optional()
132
+ });
133
+ var backupStrategySchema = z2.discriminatedUnion("kind", [
134
+ z2.object({ kind: z2.literal("file-copy") }),
135
+ z2.object({ kind: z2.literal("sqlite"), file: z2.string() }),
136
+ z2.object({ kind: z2.literal("pg-dump"), url: z2.string(), version: z2.number().int().positive() }),
137
+ z2.object({ kind: z2.literal("valkey-save"), url: z2.string() }),
138
+ z2.object({
139
+ kind: z2.literal("mysql-dump"),
140
+ url: z2.string(),
141
+ version: z2.number().int().positive()
142
+ })
143
+ ]);
144
+ var volumeSchema = z2.object({
145
+ // Refused by name: the wire is where "this was fully resolved" stops being a
146
+ // promise and becomes a check. A speculative eval fabricates a Silo so an app
147
+ // can be previewed before the operator picks one (`speculative-silo.ts`), and
148
+ // that placeholder is otherwise an ordinary absolute path - shipping it binds
149
+ // an empty directory in place of the operator's library, silently. `evalApps`
150
+ // refuses that combination up front; this is the half that cannot be bypassed.
151
+ hostPath: z2.string().refine((p) => p !== SPECULATIVE_SILO.hostPath, {
152
+ message: "the speculative Silo placeholder must never reach the wire"
153
+ }),
154
+ containerPath: z2.string(),
155
+ // The Silo group that owns this directory. The renderer turns it into
156
+ // `group_add` on every service that mounts the volume, so directory
157
+ // preparation and group membership are driven by the same field and can
158
+ // never disagree.
159
+ gid: z2.number().int().positive().optional(),
160
+ readOnly: z2.boolean().optional(),
161
+ backup: z2.union([backupStrategySchema, z2.literal(false)]).optional()
162
+ });
163
+ var wireServiceSchema = z2.object({
164
+ image: z2.string().min(1),
165
+ hostPorts: z2.array(hostPortSchema).optional(),
166
+ env: z2.record(z2.string(), z2.union([z2.string(), z2.number()])).optional(),
167
+ // containerPath -> 64-char hex digest the renderer resolves to bytes. The two
168
+ // sources hash different things: artifact refs collapse to their bare content
169
+ // sha, while inline content is keyed by *identity* — sha256 of
170
+ // `appId:serviceKey:fileKey` (plaintext rides in `WorkerApp.inlineFiles`). An
171
+ // inline file therefore keeps the same digest and worker path across content
172
+ // changes, so `--watch` consumers reload without recreating the container.
173
+ files: z2.record(z2.string().startsWith("/"), z2.string().regex(/^[a-f0-9]{64}$/)).optional(),
174
+ command: z2.array(z2.string()).optional(),
175
+ volumes: z2.array(volumeSchema).optional(),
176
+ gpu: z2.boolean().optional(),
177
+ iptables: z2.boolean().optional(),
178
+ joinNetworks: z2.array(z2.string()).optional(),
179
+ healthcheck: healthcheckSchema.optional(),
180
+ memory: z2.string().optional(),
181
+ cpu: z2.number().positive().max(128).optional(),
182
+ // Defaulted to 1 upstream, so always present on the wire.
183
+ replicas: z2.number().int().min(1).max(100),
184
+ labels: z2.array(z2.string()).optional(),
185
+ shmSizeMb: z2.number().int().positive().optional()
186
+ });
187
+ var workerAppSchema = z2.object({
188
+ appId: z2.string(),
189
+ // Flattened: in the resolve model these live under `.state` (matching
190
+ // `getDesiredState`'s shape), but on the wire only these two of that
191
+ // object's fields survive, so they're hoisted onto the app directly.
192
+ services: z2.record(nameSchema, wireServiceSchema),
193
+ /** `true` makes the per-app network `internal: true` (no egress). */
194
+ egress: z2.literal(true).optional(),
195
+ /** Worker-owned external nets this app's services join (e.g. `management-net`). */
196
+ externalNetworks: z2.array(z2.string()),
197
+ /**
198
+ * Service-name (unprefixed) -> producer edges. The renderer namespaces
199
+ * `providerServiceName` with `providerAppId` to form the final compose key.
200
+ */
201
+ dependsOn: z2.record(z2.string(), z2.array(dependsOnEdgeSchema)),
202
+ /** Plaintext for inline `files` entries, keyed by their identity digest. */
203
+ inlineFiles: z2.record(z2.string(), z2.string()),
204
+ /**
205
+ * Absolute host paths exempted from the absolute-mount gate and
206
+ * bound verbatim by the renderer (peer backup volumes, the logs socket).
207
+ */
208
+ allowedAbsoluteHostPaths: z2.array(z2.string()),
209
+ /**
210
+ * Host path the worker binds this app's `home-proxy.sock` at. Present only
211
+ * when the app holds a `home.apiAccess` grant.
212
+ */
213
+ homeProxySocketPath: z2.string().optional(),
214
+ /**
215
+ * Host path the worker binds this app's spawn broker socket at. Present
216
+ * only when the app holds a `host.spawn` grant.
217
+ */
218
+ spawnSocketPath: z2.string().optional(),
219
+ /**
220
+ * This app's registry credentials, keyed by registry host. The worker
221
+ * renders a per-app docker `config.json` from these so private images pull.
222
+ */
223
+ registryAuth: z2.record(hostKey, z2.object({ username: z2.string(), password: z2.string() })).optional()
224
+ });
225
+ var workerStateSchema = z2.object({
226
+ apps: z2.array(workerAppSchema),
227
+ /** SHAs of every artifact the worker keeps in `files/` after a reconcile. */
228
+ artifactHashes: z2.array(z2.string()),
229
+ /** Docker networks the worker container itself joins this reconcile. */
230
+ workerNetworks: z2.array(z2.string()),
231
+ /**
232
+ * Where traffic goes on this box, as `apps` says what runs on it. Runtime-
233
+ * neutral: the Docker worker renders it with `buildCaddyConfig`
234
+ * (`runtime/docker/ingress`) exactly as it renders `apps` with
235
+ * `convertToDockerCompose`.
236
+ *
237
+ * Required, not optional. Every producer can state an honest value - an
238
+ * unconfigured box gets the empty-identity placeholder, which is a real
239
+ * answer ("no domain yet") rather than a missing one - and a box with no
240
+ * schedulable app still needs a front door, so absence must never be a legal
241
+ * way to say "no ingress here".
242
+ */
243
+ ingress: ingressInputSchema
244
+ });
245
+
246
+ // src/worker-protocol/node-version.ts
247
+ import { z as z3 } from "zod";
248
+ var nodeVersionSchema = z3.object({
249
+ // The pinned ref this container runs: `benjick/realm-worker@sha256:…` for a
250
+ // pulled image, a bare image ID for a locally-loaded one (see the installer's
251
+ // pin_image). This is the string an `applied` audit entry matches against.
252
+ imageRef: z3.string().nullable(),
253
+ // The commit the image was built from - a full SHA from the publish workflow,
254
+ // null for anything built outside it.
255
+ gitHash: z3.string().nullable(),
256
+ // ISO-8601 build timestamp, read back off the image's OCI `created` label by
257
+ // the publish workflow so the reported date and the image's own can't drift.
258
+ buildDate: z3.string().nullable()
259
+ });
260
+ function readEnv(name) {
261
+ const value = Deno.env.get(name);
262
+ return value === void 0 || value === "" ? null : value;
263
+ }
264
+ function readNodeVersion() {
265
+ return {
266
+ imageRef: readEnv("REALM_IMAGE_REF"),
267
+ gitHash: readEnv("REALM_GIT_HASH"),
268
+ buildDate: readEnv("REALM_BUILD_DATE")
269
+ };
270
+ }
271
+ function shortNodeVersion(version) {
272
+ if (version.gitHash) return version.gitHash.slice(0, 7);
273
+ const digest = version.imageRef?.split("sha256:")[1];
274
+ return digest ? digest.slice(0, 12) : null;
275
+ }
276
+
277
+ // src/worker-protocol/update-files.ts
278
+ var UPDATE_ERROR_MAX_CHARS = 2048;
279
+ var MAX_ERROR_FILE_BYTES = 16 * 1024;
280
+ async function readUpdateFiles(dir) {
281
+ const [pending, error] = await Promise.all([
282
+ isFile(`${dir}/.update`),
283
+ readError(`${dir}/.update.error`)
284
+ ]);
285
+ return { pending, error };
286
+ }
287
+ async function isFile(path) {
288
+ try {
289
+ return (await Deno.stat(path)).isFile;
290
+ } catch {
291
+ return false;
292
+ }
293
+ }
294
+ async function readError(path) {
295
+ let text;
296
+ try {
297
+ const stat = await Deno.stat(path);
298
+ if (!stat.isFile || stat.size > MAX_ERROR_FILE_BYTES) return null;
299
+ text = await Deno.readTextFile(path);
300
+ } catch {
301
+ return null;
302
+ }
303
+ const trimmed = text.trim();
304
+ if (trimmed === "") return null;
305
+ return trimmed.length > UPDATE_ERROR_MAX_CHARS ? trimmed.slice(-UPDATE_ERROR_MAX_CHARS) : trimmed;
306
+ }
307
+
308
+ // src/worker-protocol/app-dirs.ts
309
+ var REALM_APP_DIR_GID = 999999;
310
+ function isPreparedAppDir(appId, volume, exemptHostPaths) {
311
+ if (volume.gid !== void 0) return false;
312
+ if (exemptHostPaths.has(volume.hostPath)) return false;
313
+ if (!volume.hostPath.startsWith("./")) return false;
314
+ return scopeBindSource(volume.hostPath, appId) !== `./apps/${appId}`;
315
+ }
316
+ function scopeBindSource(source, appId) {
317
+ if (!source.startsWith("./")) return source;
318
+ const scoped = `./apps/${appId}/${source.slice(2)}`;
319
+ const segments = scoped.split("/");
320
+ const resolved = [];
321
+ for (const seg of segments) {
322
+ if (seg === "..") resolved.pop();
323
+ else if (seg !== "." && seg !== "") resolved.push(seg);
324
+ }
325
+ const normalized = "./" + resolved.join("/");
326
+ if (!normalized.startsWith(`./apps/${appId}/`) && normalized !== `./apps/${appId}`) {
327
+ throw new Error(`bind source escapes app directory: ${source}`);
328
+ }
329
+ return normalized;
330
+ }
331
+
332
+ // src/worker-protocol/silo-paths.ts
333
+ var REALM_SILO_DIR = "/opt/realm/worker/silo";
334
+ function managedSiloPath(id) {
335
+ return `${REALM_SILO_DIR}/${id}`;
336
+ }
337
+ function isManagedSiloPath(hostPath) {
338
+ return hostPath.startsWith(`${REALM_SILO_DIR}/`);
339
+ }
340
+ function shellQuote(path) {
341
+ return `'${path.replaceAll("'", "'\\''")}'`;
342
+ }
343
+ function siloSetupCommand(silo) {
344
+ const path = shellQuote(silo.hostPath);
345
+ return `chgrp -R ${silo.gid} ${path}
346
+ chmod g+rwxs ${path}`;
347
+ }
348
+ function siloWidenCommand(silo) {
349
+ return `find ${shellQuote(silo.hostPath)} -type d -exec chmod g+rwxs {} +`;
350
+ }
351
+ function siloReclaimCommand(orphan) {
352
+ return `ssh root@${orphan.workerIp}
353
+ rm -rf ${shellQuote(orphan.hostPath)}`;
354
+ }
355
+
356
+ export {
357
+ ingressInputSchema,
358
+ PLATFORM_INGRESS_PORTS,
359
+ dependsOnEdgeSchema,
360
+ wireServiceSchema,
361
+ workerAppSchema,
362
+ workerStateSchema,
363
+ nodeVersionSchema,
364
+ readNodeVersion,
365
+ shortNodeVersion,
366
+ readUpdateFiles,
367
+ REALM_APP_DIR_GID,
368
+ isPreparedAppDir,
369
+ scopeBindSource,
370
+ REALM_SILO_DIR,
371
+ managedSiloPath,
372
+ isManagedSiloPath,
373
+ siloSetupCommand,
374
+ siloWidenCommand,
375
+ siloReclaimCommand
376
+ };
@@ -0,0 +1,139 @@
1
+ // src/ref.ts
2
+ var REF_SYMBOL = /* @__PURE__ */ Symbol.for("realm.ref");
3
+ var DEFERRED_SYMBOL = /* @__PURE__ */ Symbol.for("realm.deferred");
4
+ function isRef(value) {
5
+ return typeof value === "object" && value !== null && REF_SYMBOL in value;
6
+ }
7
+ function containsRef(value) {
8
+ if (isRef(value) || isRefMarker(value)) return true;
9
+ if (Array.isArray(value)) return value.some(containsRef);
10
+ if (typeof value === "object" && value !== null) {
11
+ for (const v of Object.values(value)) {
12
+ if (containsRef(v)) return true;
13
+ }
14
+ }
15
+ return false;
16
+ }
17
+ function isRefMarker(value) {
18
+ return typeof value === "object" && value !== null && "__realmRef" in value && typeof value.__realmRef === "object";
19
+ }
20
+ function throwOnToString() {
21
+ throw new Error(
22
+ "Refs cannot be stringified directly. Use interpolate`...` for templates or pass the ref through as an env/command/volume value."
23
+ );
24
+ }
25
+ function identityRef(field) {
26
+ return {
27
+ [REF_SYMBOL]: true,
28
+ toJSON: () => ({ __realmRef: { kind: "identity", field } }),
29
+ toString: throwOnToString
30
+ };
31
+ }
32
+ function ref(marker) {
33
+ return {
34
+ [REF_SYMBOL]: true,
35
+ toJSON: () => ({ __realmRef: marker }),
36
+ toString: throwOnToString
37
+ };
38
+ }
39
+ function stringRef(resolve) {
40
+ return {
41
+ [REF_SYMBOL]: true,
42
+ toJSON: resolve,
43
+ toString: throwOnToString
44
+ };
45
+ }
46
+ function deferredRef(marker) {
47
+ return {
48
+ [DEFERRED_SYMBOL]: true,
49
+ toJSON: () => ({ __realmRef: marker }),
50
+ toString: throwOnToString
51
+ };
52
+ }
53
+ function interpolate(strings, ...values) {
54
+ return {
55
+ [REF_SYMBOL]: true,
56
+ toJSON() {
57
+ const parts = [];
58
+ let allStrings = true;
59
+ for (let i = 0; i < strings.length; i++) {
60
+ const literal = strings[i];
61
+ if (literal.length > 0) parts.push(literal);
62
+ if (i < values.length) {
63
+ const val = values[i];
64
+ let marker = null;
65
+ if (isRef(val)) {
66
+ const resolved = val.toJSON();
67
+ if (typeof resolved === "object" && resolved !== null && "__realmRef" in resolved) {
68
+ marker = resolved.__realmRef;
69
+ } else {
70
+ parts.push(String(resolved));
71
+ }
72
+ } else if (isRefMarker(val)) {
73
+ const m = val.__realmRef;
74
+ if (m.kind === "deferred") {
75
+ throw new Error(
76
+ "interpolate cannot accept a deferred ref; move affix logic into apply()"
77
+ );
78
+ }
79
+ marker = m;
80
+ } else {
81
+ parts.push(String(val));
82
+ }
83
+ if (marker) {
84
+ if (marker.kind === "concat") {
85
+ for (const p of marker.parts) {
86
+ parts.push(p);
87
+ if (typeof p !== "string") allStrings = false;
88
+ }
89
+ } else {
90
+ parts.push(marker);
91
+ allStrings = false;
92
+ }
93
+ }
94
+ }
95
+ }
96
+ if (allStrings) return parts.join("");
97
+ return { __realmRef: { kind: "concat", parts } };
98
+ },
99
+ toString: throwOnToString
100
+ };
101
+ }
102
+
103
+ // src/service.ts
104
+ var SELF_APP_ID = "__self__";
105
+ var Service = class {
106
+ _serviceKey;
107
+ /** Called by the framework after scanning the services map */
108
+ _setServiceKey(key) {
109
+ this._serviceKey = key;
110
+ }
111
+ /**
112
+ * A per-service derived secret. Within the same app, same-named secrets
113
+ * collide intentionally (Postgres.password and Valkey.password in the
114
+ * same app share a hash) - that's the ergonomic contract.
115
+ */
116
+ derived(name) {
117
+ return ref({ kind: "derived", appId: SELF_APP_ID, name });
118
+ }
119
+ /**
120
+ * DNS name of this service within the per-app compose project. Resolves
121
+ * to a plain string at serialization time - `_setServiceKey` is
122
+ * guaranteed to have run before `getServiceDefinition`, which is the
123
+ * only time a `host` ref is serialized.
124
+ */
125
+ get host() {
126
+ return stringRef(() => this._serviceKey ?? "__unresolved__");
127
+ }
128
+ };
129
+
130
+ export {
131
+ REF_SYMBOL,
132
+ containsRef,
133
+ identityRef,
134
+ ref,
135
+ deferredRef,
136
+ interpolate,
137
+ SELF_APP_ID,
138
+ Service
139
+ };
@@ -0,0 +1,6 @@
1
+ // src/home-cookie.ts
2
+ var HOME_COOKIE_PREFIX = "realm-home";
3
+
4
+ export {
5
+ HOME_COOKIE_PREFIX
6
+ };
@@ -0,0 +1,9 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ export {
8
+ __export
9
+ };