@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.
- package/LICENSE-EXCEPTION +26 -0
- package/README.md +34 -0
- package/dist/chunk-3R6KEFOI.js +22 -0
- package/dist/chunk-67BDAMYX.js +376 -0
- package/dist/chunk-AITGS27R.js +139 -0
- package/dist/chunk-CMAEHQ4F.js +6 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-O3UIK34B.js +1430 -0
- package/dist/chunk-VHTUSZUX.js +176 -0
- package/dist/cli.js +9006 -0
- package/dist/docker.js +54 -0
- package/dist/index.js +1053 -0
- package/dist/ingress.js +436 -0
- package/dist/jsx-runtime.js +2 -0
- package/dist/preact.js +22 -0
- package/dist/service-url.js +11 -0
- package/dist/services.js +213 -0
- package/dist/test.js +5069 -0
- package/dist/ui.js +254 -0
- package/dist/worker-protocol.js +89 -0
- package/package.json +112 -0
- package/src/app-icon.ts +74 -0
- package/src/apply.ts +52 -0
- package/src/bundle-ui.ts +211 -0
- package/src/bundle.ts +546 -0
- package/src/cli/build-dev-landing.ts +84 -0
- package/src/cli/build-install-panel.ts +90 -0
- package/src/cli/build-runtime.ts +133 -0
- package/src/cli/build.ts +63 -0
- package/src/cli/dev.ts +107 -0
- package/src/cli/down.ts +60 -0
- package/src/cli/main.ts +56 -0
- package/src/cli/test.ts +61 -0
- package/src/cli/validate.ts +37 -0
- package/src/compile.ts +100 -0
- package/src/create-app.ts +982 -0
- package/src/deep-merge.ts +33 -0
- package/src/dev/fixture.ts +48 -0
- package/src/dev/install-handler.ts +383 -0
- package/src/dev/server.ts +968 -0
- package/src/dev/stack.ts +108 -0
- package/src/dev/state.ts +159 -0
- package/src/docker.ts +96 -0
- package/src/errors.ts +61 -0
- package/src/factory.ts +481 -0
- package/src/garage.ts +56 -0
- package/src/generator/hash.ts +6 -0
- package/src/generator/resolve-refs.ts +207 -0
- package/src/grants-builder.ts +100 -0
- package/src/grants.ts +260 -0
- package/src/home-api.ts +172 -0
- package/src/home-cookie.ts +10 -0
- package/src/home-registry.ts +120 -0
- package/src/home.ts +187 -0
- package/src/host-registry.ts +143 -0
- package/src/http-header.ts +20 -0
- package/src/identifiers.ts +49 -0
- package/src/index.ts +66 -0
- package/src/info.ts +64 -0
- package/src/ini.ts +50 -0
- package/src/json.ts +14 -0
- package/src/jsx-runtime/index.ts +5 -0
- package/src/keys.ts +29 -0
- package/src/mysql.ts +56 -0
- package/src/pack-build.ts +108 -0
- package/src/pack.ts +115 -0
- package/src/peer-registry.ts +121 -0
- package/src/peer-world.ts +117 -0
- package/src/permission-meta.ts +116 -0
- package/src/permissions.ts +108 -0
- package/src/pkg-root.ts +34 -0
- package/src/postgres.ts +79 -0
- package/src/preact/index.ts +27 -0
- package/src/redis.ts +43 -0
- package/src/ref.ts +271 -0
- package/src/remote-registry.ts +33 -0
- package/src/required-host-kinds.ts +110 -0
- package/src/resolve-context.ts +111 -0
- package/src/resolve.ts +471 -0
- package/src/runtime/docker/compose/compose-schema.ts +118 -0
- package/src/runtime/docker/compose/convert-to-docker-compose.ts +278 -0
- package/src/runtime/docker/compose/default-hardening.ts +21 -0
- package/src/runtime/docker/compose/effects.ts +107 -0
- package/src/runtime/docker/compose/index.ts +17 -0
- package/src/runtime/docker/compose/labels.ts +39 -0
- package/src/runtime/docker/compose/types.ts +78 -0
- package/src/runtime/docker/home-proxy-server/index.ts +177 -0
- package/src/runtime/docker/home-proxy-server/protocol.ts +53 -0
- package/src/runtime/docker/ingress/build-config.ts +819 -0
- package/src/runtime/docker/ingress/index.ts +16 -0
- package/src/runtime/docker/ingress/types.ts +182 -0
- package/src/runtime/docker/log-frames.ts +121 -0
- package/src/runtime/docker/logs-server/index.ts +366 -0
- package/src/runtime/docker/spawn-server/context-key.ts +82 -0
- package/src/runtime/docker/spawn-server/index.ts +492 -0
- package/src/runtime/docker/spawn-server/protocol.ts +70 -0
- package/src/runtime/docker/spawn-server/spec.ts +122 -0
- package/src/runtime/docker/stats-server/disks.ts +161 -0
- package/src/runtime/docker/stats-server/index.ts +322 -0
- package/src/runtime/home-client/index.ts +71 -0
- package/src/runtime/log-line.ts +24 -0
- package/src/runtime/logs-client/index.ts +175 -0
- package/src/runtime/spawn-client/index.ts +93 -0
- package/src/runtime/spawn-client/kaniko.ts +79 -0
- package/src/runtime/stats-client/index.ts +65 -0
- package/src/runtime/stats-sample.ts +95 -0
- package/src/safe-json.ts +37 -0
- package/src/sandbox.ts +1209 -0
- package/src/schema/build-apps.ts +38 -0
- package/src/schema/home.ts +217 -0
- package/src/schema/host.ts +792 -0
- package/src/schema/inputs.ts +26 -0
- package/src/schema/nodes.ts +75 -0
- package/src/schema/peer.ts +400 -0
- package/src/schema/public-domain.ts +116 -0
- package/src/schema/remote.ts +74 -0
- package/src/security.ts +594 -0
- package/src/serialize.ts +21 -0
- package/src/service-definition.ts +284 -0
- package/src/service-url.ts +64 -0
- package/src/service.ts +39 -0
- package/src/services/index.ts +5 -0
- package/src/shared.ts +65 -0
- package/src/speculative-silo.ts +96 -0
- package/src/stable-secret.ts +14 -0
- package/src/surfaces.ts +264 -0
- package/src/tailwind-bin.ts +56 -0
- package/src/test/boot.ts +192 -0
- package/src/test/compile.ts +501 -0
- package/src/test/describe.ts +117 -0
- package/src/test/index.ts +11 -0
- package/src/test/ingress-fixture.ts +37 -0
- package/src/test/types.ts +318 -0
- package/src/trusted-headers.ts +79 -0
- package/src/types.ts +126 -0
- package/src/ui/components/aside.tsx +96 -0
- package/src/ui/components/badge.tsx +42 -0
- package/src/ui/components/button.tsx +59 -0
- package/src/ui/components/card.tsx +106 -0
- package/src/ui/components/checkbox.tsx +37 -0
- package/src/ui/components/code-block.tsx +29 -0
- package/src/ui/components/combobox.tsx +119 -0
- package/src/ui/components/config-field.tsx +284 -0
- package/src/ui/components/dialog.tsx +137 -0
- package/src/ui/components/domain-editor.tsx +317 -0
- package/src/ui/components/domain-list.ts +205 -0
- package/src/ui/components/dropdown-menu.tsx +73 -0
- package/src/ui/components/empty-state.tsx +51 -0
- package/src/ui/components/highlight.ts +59 -0
- package/src/ui/components/icons.tsx +101 -0
- package/src/ui/components/image-posture.tsx +85 -0
- package/src/ui/components/input.tsx +28 -0
- package/src/ui/components/label.tsx +16 -0
- package/src/ui/components/line-chart.tsx +113 -0
- package/src/ui/components/page.tsx +39 -0
- package/src/ui/components/permission-row.tsx +262 -0
- package/src/ui/components/permission-section.tsx +51 -0
- package/src/ui/components/pie-chart.tsx +245 -0
- package/src/ui/components/schema-block.tsx +60 -0
- package/src/ui/components/separator.tsx +42 -0
- package/src/ui/components/sheet.tsx +130 -0
- package/src/ui/components/sidebar.tsx +650 -0
- package/src/ui/components/silo-picker.tsx +510 -0
- package/src/ui/components/skeleton.tsx +13 -0
- package/src/ui/components/sonner.tsx +33 -0
- package/src/ui/components/switch.tsx +58 -0
- package/src/ui/components/table-view.ts +219 -0
- package/src/ui/components/table.tsx +506 -0
- package/src/ui/components/tabs.tsx +59 -0
- package/src/ui/components/timeago.tsx +84 -0
- package/src/ui/components/tooltip.tsx +56 -0
- package/src/ui/components.json +21 -0
- package/src/ui/dev/dev-shell.tsx +145 -0
- package/src/ui/home/home-grid.tsx +177 -0
- package/src/ui/hooks/use-mobile.ts +19 -0
- package/src/ui/index.ts +189 -0
- package/src/ui/install/adapter.ts +193 -0
- package/src/ui/install/deploy-targets-picker.tsx +185 -0
- package/src/ui/install/install-app-panel.tsx +912 -0
- package/src/ui/install/install-error.tsx +62 -0
- package/src/ui/install/perms-inactive.ts +50 -0
- package/src/ui/install/required-fields.ts +37 -0
- package/src/ui/install/tabs-form.tsx +557 -0
- package/src/ui/tokens.css +279 -0
- package/src/ui/utils.ts +78 -0
- package/src/ui-error.ts +139 -0
- package/src/ui-runtime/dev-landing-entry.tsx +258 -0
- package/src/ui-runtime/index.ts +83 -0
- package/src/ui-runtime/install-entry.tsx +119 -0
- package/src/ui-runtime/mount.ts +367 -0
- package/src/ui-runtime/runtime.css +56 -0
- package/src/ui-surface.ts +451 -0
- package/src/valkey.ts +43 -0
- package/src/worker/extract-tar.ts +125 -0
- package/src/worker/identity.ts +183 -0
- package/src/worker/index.ts +16 -0
- package/src/worker/public-ip.ts +92 -0
- package/src/worker/queue.ts +54 -0
- package/src/worker/reconcile.ts +127 -0
- package/src/worker/start-worker.ts +227 -0
- package/src/worker/ui-proxy.ts +335 -0
- package/src/worker/uplink.ts +523 -0
- package/src/worker/validate.ts +34 -0
- package/src/worker/verify-relay.ts +102 -0
- package/src/worker-protocol/app-dirs.ts +94 -0
- package/src/worker-protocol/home-proxy-paths.ts +76 -0
- package/src/worker-protocol/index.ts +55 -0
- package/src/worker-protocol/ingress.ts +166 -0
- package/src/worker-protocol/logs-socket-path.ts +50 -0
- package/src/worker-protocol/networks.ts +39 -0
- package/src/worker-protocol/node-version.ts +70 -0
- package/src/worker-protocol/silo-paths.ts +114 -0
- package/src/worker-protocol/spawn-paths.ts +165 -0
- package/src/worker-protocol/stats-socket-path.ts +40 -0
- package/src/worker-protocol/update-files.ts +77 -0
- package/src/worker-protocol/uplink/constants.ts +86 -0
- package/src/worker-protocol/uplink/errors.ts +61 -0
- package/src/worker-protocol/uplink/frame.ts +114 -0
- package/src/worker-protocol/uplink/identity.ts +40 -0
- package/src/worker-protocol/uplink/index.ts +63 -0
- package/src/worker-protocol/uplink/methods.ts +462 -0
- package/src/worker-protocol/uplink/mux.ts +646 -0
- package/src/worker-protocol/uplink/protocol.ts +11 -0
- package/src/worker-protocol/uplink/rpc.ts +309 -0
- package/src/worker-protocol/uplink/session.ts +540 -0
- package/src/worker-protocol/uplink/test-support.ts +188 -0
- package/src/worker-protocol/worker-mount.ts +27 -0
- package/src/worker-protocol/worker-state.ts +190 -0
- package/src/xml.ts +49 -0
- package/src/yaml.ts +15 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import type { AppIcon } from "./app-icon.ts";
|
|
2
|
+
import type { Network } from "./identifiers.ts";
|
|
3
|
+
import type { Resolvable } from "./ref.ts";
|
|
4
|
+
import type { Artifact } from "./schema/remote.ts";
|
|
5
|
+
import type { PeerHostPath } from "./schema/peer.ts";
|
|
6
|
+
import type { SiloHostPath } from "./schema/host.ts";
|
|
7
|
+
import type { PublicDomainValue } from "./schema/public-domain.ts";
|
|
8
|
+
import type { WorkerHostPath } from "./worker-protocol/logs-socket-path.ts";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build a service's image from source instead of pulling one. The recipe
|
|
12
|
+
* only - no `repo` / `branch` (those are the build service's binding, set
|
|
13
|
+
* elsewhere). A service carries either `image` or `build`, never both and
|
|
14
|
+
* never neither (enforced at the type level on `ServiceDefinition` and again
|
|
15
|
+
* at runtime in `homeServiceSchema`). A `build:` service has no pinned
|
|
16
|
+
* digest until its first build, so Home leaves it inert (installed, not
|
|
17
|
+
* reconciled) until one is set.
|
|
18
|
+
*/
|
|
19
|
+
export interface BuildRecipe {
|
|
20
|
+
/** Path to the Dockerfile within the build context. Defaults to `Dockerfile`. */
|
|
21
|
+
dockerfile?: string;
|
|
22
|
+
/** `--build-arg` values passed to the builder, keyed by arg name. */
|
|
23
|
+
buildArgs?: Record<string, string>;
|
|
24
|
+
/** Sub-path within the build context to build from. Defaults to the context root. */
|
|
25
|
+
context?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The `router` block of a routed (single-app) service. Split out of the
|
|
30
|
+
* common base so `SystemServiceDefinition` can exclude it wholesale:
|
|
31
|
+
* system apps have no routes.
|
|
32
|
+
*/
|
|
33
|
+
interface ServiceRouter {
|
|
34
|
+
/**
|
|
35
|
+
* Human-readable label shown on the Home home screen tile. Falls back
|
|
36
|
+
* to the app's top-level `name` when omitted.
|
|
37
|
+
*/
|
|
38
|
+
name?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Icon for the tile: a `dashboard-icons:<key>` or `selfhst:<key>` pack key,
|
|
41
|
+
* or an https URL to an svg/png/webp/jpeg. Home fetches it once and serves
|
|
42
|
+
* it same-origin, so the browser never reaches the source.
|
|
43
|
+
*/
|
|
44
|
+
icon?: AppIcon;
|
|
45
|
+
/**
|
|
46
|
+
* Show a clickable tile for this service on the Home home screen.
|
|
47
|
+
* Defaults to `true`. Set `false` for machine-facing routes that need
|
|
48
|
+
* the gated subdomain but aren't a human-browsable app (e.g. a webhook
|
|
49
|
+
* receiver or a registry endpoint) — the route still works, it just
|
|
50
|
+
* doesn't appear as a home tile.
|
|
51
|
+
*/
|
|
52
|
+
tile?: boolean;
|
|
53
|
+
/** Port inside the container the proxy forwards to. */
|
|
54
|
+
containerPort: number;
|
|
55
|
+
/**
|
|
56
|
+
* Serve this service on the operator's own domain(s), in addition to
|
|
57
|
+
* the authenticated subdomain it always gets.
|
|
58
|
+
*
|
|
59
|
+
* Takes the resolved value of a `r.home.publicDomain()` permission whole -
|
|
60
|
+
* hostnames and the operator's redirect choice travel together, so wiring
|
|
61
|
+
* the domains can't leave the mode behind. A literal string is deliberately
|
|
62
|
+
* not accepted: the operator supplies the name, the author only says where
|
|
63
|
+
* it lands. Unconfigured (`{ hosts: [] }`) emits no public route.
|
|
64
|
+
*/
|
|
65
|
+
publicDomain?: PublicDomainValue;
|
|
66
|
+
/**
|
|
67
|
+
* Route only requests matching this path prefix. The prefix is
|
|
68
|
+
* stripped before forwarding to the container.
|
|
69
|
+
*/
|
|
70
|
+
pathPrefix?: string;
|
|
71
|
+
/** Enable response compression (gzip / zstd). */
|
|
72
|
+
compress?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Max request body size in megabytes. `null` disables the cap.
|
|
75
|
+
*/
|
|
76
|
+
maxRequestSizeMb?: number | null;
|
|
77
|
+
/**
|
|
78
|
+
* Per-IP sliding-window request budget: no more than `requests`
|
|
79
|
+
* in any `window` (a duration like `"10s"` / `"1m"`). A bare number
|
|
80
|
+
* is shorthand for that many requests per second. Defaults to 1000 per
|
|
81
|
+
* 10s, a runaway-client ceiling rather than a browsing budget; tighten
|
|
82
|
+
* it on routes that need it. `null` disables rate limiting.
|
|
83
|
+
*/
|
|
84
|
+
rateLimit?: number | { requests: number; window: string } | null;
|
|
85
|
+
/**
|
|
86
|
+
* Static response headers to add. A `null` value removes a default
|
|
87
|
+
* header the proxy would otherwise set.
|
|
88
|
+
*/
|
|
89
|
+
headers?: Record<string, string | null>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Fields shared by every service, independent of whether it pulls an
|
|
94
|
+
* `image` or carries a `build` recipe — and independent of whether it may
|
|
95
|
+
* route. Not exported - `ServiceDefinition` / `SystemServiceDefinition`
|
|
96
|
+
* are the public surfaces, XORs over this base. `router` is layered on by
|
|
97
|
+
* `ServiceCommon` (single apps only): system apps have no routes, so
|
|
98
|
+
* `SystemServiceDefinition` builds on this routeless base directly.
|
|
99
|
+
*/
|
|
100
|
+
interface RoutelessServiceCommon {
|
|
101
|
+
/**
|
|
102
|
+
* Bind container ports to ports on the worker host. Each entry maps a
|
|
103
|
+
* `containerPort` to a `hostPort` (TCP unless `protocol` says
|
|
104
|
+
* otherwise). Every `hostPort` requires a matching `host.port` grant.
|
|
105
|
+
*/
|
|
106
|
+
hostPorts?: Array<{ containerPort: number; hostPort: number; protocol?: "tcp" | "udp" }>;
|
|
107
|
+
/**
|
|
108
|
+
* Environment variables passed into the container. Values can be
|
|
109
|
+
* plain strings/numbers or refs (derived secrets, peer outputs, etc.)
|
|
110
|
+
* that resolve at deploy time.
|
|
111
|
+
*/
|
|
112
|
+
env?: Record<string, string | Resolvable<string> | number>;
|
|
113
|
+
/**
|
|
114
|
+
* Files mounted read-only into the container, keyed by absolute
|
|
115
|
+
* container path. Values are inline text, a `remote.artifact` ref, or
|
|
116
|
+
* a resolvable string. Useful for config files the app reads at
|
|
117
|
+
* startup.
|
|
118
|
+
*/
|
|
119
|
+
files?: Record<string, string | Artifact | Resolvable<string>>;
|
|
120
|
+
/** Override the image's default command (entrypoint args). */
|
|
121
|
+
command?: (string | Resolvable<string>)[];
|
|
122
|
+
/**
|
|
123
|
+
* Bind-mount directories from the host into the container. Relative
|
|
124
|
+
* `./...` paths are scoped to the app's data dir; absolute paths only
|
|
125
|
+
* come from resolved permissions (a Silo's volume, broker socket dirs).
|
|
126
|
+
*/
|
|
127
|
+
volumes?: Array<{
|
|
128
|
+
/**
|
|
129
|
+
* Source path on the host. Relative `./...` is scoped to the app's
|
|
130
|
+
* own data dir under `/opt/realm/`; absolute paths come from resolved
|
|
131
|
+
* permissions and are gated against them.
|
|
132
|
+
*/
|
|
133
|
+
hostPath: `./${string}` | PeerHostPath | WorkerHostPath | SiloHostPath | Resolvable<string>;
|
|
134
|
+
/** Mount point inside the container. */
|
|
135
|
+
containerPath: string;
|
|
136
|
+
/**
|
|
137
|
+
* Group that owns the mounted directory. Set by a Silo, never written by
|
|
138
|
+
* hand - the renderer turns it into `group_add` on every service that
|
|
139
|
+
* mounts this volume, and the grant gate matches the (hostPath, gid) pair
|
|
140
|
+
* against the Silos the app was bound to.
|
|
141
|
+
*/
|
|
142
|
+
gid?: number;
|
|
143
|
+
/**
|
|
144
|
+
* Mount read-only. Opt-in: a mount an app declared is a mount it means to
|
|
145
|
+
* use, and writing to it is the common case. A Silo's volume entry sets
|
|
146
|
+
* this from the mode the app asked for (`volume: "read"` -> `true`).
|
|
147
|
+
*/
|
|
148
|
+
readOnly?: boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Backup strategy for this volume. `file-copy` snapshots files via
|
|
151
|
+
* restic; `sqlite` runs an online `.backup` against the named file;
|
|
152
|
+
* the dump strategies (`pg-dump`, `valkey-save`, `mysql-dump`)
|
|
153
|
+
* snapshot via the appropriate dump tool. `false` opts out.
|
|
154
|
+
*/
|
|
155
|
+
backup?:
|
|
156
|
+
| { kind: "file-copy" }
|
|
157
|
+
| { kind: "sqlite"; file: string }
|
|
158
|
+
| { kind: "pg-dump"; url: string | Resolvable<string>; version: number }
|
|
159
|
+
| { kind: "valkey-save"; url: string | Resolvable<string> }
|
|
160
|
+
| { kind: "mysql-dump"; url: string | Resolvable<string>; version: number }
|
|
161
|
+
| false;
|
|
162
|
+
}>;
|
|
163
|
+
/** Pass through the host's GPU. Requires `host.gpu` grant. */
|
|
164
|
+
gpu?: boolean;
|
|
165
|
+
/**
|
|
166
|
+
* Install host-level iptables / ip6tables rules from this container.
|
|
167
|
+
* Expands to `cap_add: NET_ADMIN` + `network_mode: host` at render
|
|
168
|
+
* time. Requires `host.iptables` grant.
|
|
169
|
+
*/
|
|
170
|
+
iptables?: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Extra Docker networks to join (e.g. another app's network when this
|
|
173
|
+
* app has a `peers.routedApps` or `peers.backupVolumes` grant).
|
|
174
|
+
*/
|
|
175
|
+
joinNetworks?: Network[];
|
|
176
|
+
/**
|
|
177
|
+
* Container healthcheck. The reconciler waits for `healthy` before
|
|
178
|
+
* declaring the deploy successful, so failing healthchecks block
|
|
179
|
+
* rollout instead of silently leaving a broken container running.
|
|
180
|
+
*/
|
|
181
|
+
healthcheck?: {
|
|
182
|
+
/**
|
|
183
|
+
* Healthcheck command in Docker exec form, e.g.
|
|
184
|
+
* `["CMD", "curl", "-f", "http://localhost"]`.
|
|
185
|
+
*/
|
|
186
|
+
test: string[];
|
|
187
|
+
/** Seconds between checks. */
|
|
188
|
+
interval?: number;
|
|
189
|
+
/** Seconds before a check is considered failed. */
|
|
190
|
+
timeout?: number;
|
|
191
|
+
/** Failed checks before the container is marked unhealthy. */
|
|
192
|
+
retries?: number;
|
|
193
|
+
/**
|
|
194
|
+
* Grace period in seconds. Failed checks during this window don't
|
|
195
|
+
* count toward `retries`.
|
|
196
|
+
*/
|
|
197
|
+
startPeriod?: number;
|
|
198
|
+
};
|
|
199
|
+
/** Hard memory limit (e.g. `"512m"`, `"1g"`). */
|
|
200
|
+
memory?: string;
|
|
201
|
+
/** Hard CPU limit, fractional cores allowed. */
|
|
202
|
+
cpu?: number;
|
|
203
|
+
/** Number of identical containers to run for this service. */
|
|
204
|
+
replicas?: number;
|
|
205
|
+
/**
|
|
206
|
+
* Additional Docker labels on the container. The `realm.*` namespace
|
|
207
|
+
* is reserved.
|
|
208
|
+
*/
|
|
209
|
+
labels?: string[];
|
|
210
|
+
/**
|
|
211
|
+
* Shared memory size in megabytes (`/dev/shm`). Bumped for apps that
|
|
212
|
+
* use a lot of shared memory (e.g. browsers).
|
|
213
|
+
*/
|
|
214
|
+
shmSizeMb?: number;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* A single-app service: the routeless base plus the `router` block.
|
|
219
|
+
*/
|
|
220
|
+
interface ServiceCommon extends RoutelessServiceCommon {
|
|
221
|
+
/**
|
|
222
|
+
* Expose this service on the proxy. The container is reached over
|
|
223
|
+
* TLS at the configured public domain - the proxy app handles
|
|
224
|
+
* routing, TLS, and headers.
|
|
225
|
+
*/
|
|
226
|
+
router?: ServiceRouter;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* A service either pulls a prebuilt `image` or carries a `build` recipe -
|
|
231
|
+
* exactly one, never both, never neither. The `?: never` arms make
|
|
232
|
+
* supplying the wrong one (or omitting both) a compile-time error in the
|
|
233
|
+
* realm.ts author's editor and under `realm-sdk test`; `homeServiceSchema`
|
|
234
|
+
* enforces the same rule at runtime for sandbox-produced state.
|
|
235
|
+
*/
|
|
236
|
+
export type ServiceDefinition =
|
|
237
|
+
| (ServiceCommon & {
|
|
238
|
+
/**
|
|
239
|
+
* Docker image reference. A tag (`postgres:16`) is fine; Home
|
|
240
|
+
* re-pins it to a SHA digest at install time so every worker pulls
|
|
241
|
+
* the exact same bytes.
|
|
242
|
+
*/
|
|
243
|
+
image: string;
|
|
244
|
+
build?: never;
|
|
245
|
+
})
|
|
246
|
+
| (ServiceCommon & {
|
|
247
|
+
/**
|
|
248
|
+
* Build this service's image from source. Mutually exclusive with `image`.
|
|
249
|
+
* `true` is sugar for `{}` (root `Dockerfile`, no build args); pass a
|
|
250
|
+
* `BuildRecipe` to point at a non-default Dockerfile / context / args.
|
|
251
|
+
*/
|
|
252
|
+
build: true | BuildRecipe;
|
|
253
|
+
image?: never;
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* A system-app service: the same image/build XOR, but over the routeless
|
|
258
|
+
* base with `router` tombstoned. System apps have no routes — no
|
|
259
|
+
* authenticated subdomains, no public domains; genuine network exposure
|
|
260
|
+
* goes through `host.port`. The XOR is
|
|
261
|
+
* restated rather than `Omit<>`-ed off `ServiceDefinition` because
|
|
262
|
+
* `Omit` over a union doesn't distribute.
|
|
263
|
+
*/
|
|
264
|
+
export type SystemServiceDefinition =
|
|
265
|
+
| (RoutelessServiceCommon & {
|
|
266
|
+
/**
|
|
267
|
+
* Docker image reference. A tag (`postgres:16`) is fine; Home
|
|
268
|
+
* re-pins it to a SHA digest at install time so every worker pulls
|
|
269
|
+
* the exact same bytes.
|
|
270
|
+
*/
|
|
271
|
+
image: string;
|
|
272
|
+
build?: never;
|
|
273
|
+
router?: never;
|
|
274
|
+
})
|
|
275
|
+
| (RoutelessServiceCommon & {
|
|
276
|
+
/**
|
|
277
|
+
* Build this service's image from source. Mutually exclusive with `image`.
|
|
278
|
+
* `true` is sugar for `{}` (root `Dockerfile`, no build args); pass a
|
|
279
|
+
* `BuildRecipe` to point at a non-default Dockerfile / context / args.
|
|
280
|
+
*/
|
|
281
|
+
build: true | BuildRecipe;
|
|
282
|
+
image?: never;
|
|
283
|
+
router?: never;
|
|
284
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Service URL resolution — the single place that turns an
|
|
2
|
+
// `(appId, serviceId, slug, baseDomain)` tuple into the external host a
|
|
3
|
+
// service is reachable at. Pure string logic, no host globals, so it stays
|
|
4
|
+
// QuickJS-load-safe when pulled in through the `@yourrealm/sdk/compile` barrel.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The structured authenticated subdomain host for a routed service:
|
|
8
|
+
* `{appId}-{serviceId}.{slug}.{baseDomain}`. `{appId}-{serviceId}` is a
|
|
9
|
+
* single DNS label (appId already carries its install nanoid), one level
|
|
10
|
+
* under the per-worker wildcard `*.{slug}.{baseDomain}` the acme-dns policy
|
|
11
|
+
* issues a cert for. Host only — callers prefix the scheme.
|
|
12
|
+
*/
|
|
13
|
+
export function structuredHost(
|
|
14
|
+
appId: string,
|
|
15
|
+
serviceId: string,
|
|
16
|
+
slug: string,
|
|
17
|
+
baseDomain: string,
|
|
18
|
+
): string {
|
|
19
|
+
return `${appId}-${serviceId}.${slug}.${baseDomain}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a service to its full external `https://` URL, or `null` when it
|
|
24
|
+
* has no reachable URL. The rules, in order:
|
|
25
|
+
*
|
|
26
|
+
* 1. No `router` → no external URL at all → `null`.
|
|
27
|
+
* 2. A `publicDomain` set on the router → `https://{publicDomain}`.
|
|
28
|
+
* 3. Otherwise the structured authenticated subdomain Home provisions —
|
|
29
|
+
* needs both the placement `slug` and the Home's `baseDomain`; `null`
|
|
30
|
+
* if either is missing (install not yet on a worker / Home has no base
|
|
31
|
+
* domain).
|
|
32
|
+
*/
|
|
33
|
+
export function serviceUrl(args: {
|
|
34
|
+
appId: string;
|
|
35
|
+
serviceId: string;
|
|
36
|
+
slug: string | null;
|
|
37
|
+
baseDomain: string | null;
|
|
38
|
+
/** Resolved `router.publicDomain` (first entry if it was an array), if any. */
|
|
39
|
+
publicDomain?: string;
|
|
40
|
+
hasRouter: boolean;
|
|
41
|
+
}): string | null {
|
|
42
|
+
if (!args.hasRouter) return null;
|
|
43
|
+
if (args.publicDomain) return `https://${args.publicDomain}`;
|
|
44
|
+
if (!args.slug || !args.baseDomain) return null;
|
|
45
|
+
return `https://${structuredHost(args.appId, args.serviceId, args.slug, args.baseDomain)}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The canonical host out of a router's `publicDomain` - the resolved
|
|
50
|
+
* `r.home.publicDomain()` record, whose `hosts[0]` is canonical by
|
|
51
|
+
* convention. `undefined` for an absent record or an empty `hosts` (the
|
|
52
|
+
* operator enabled the permission but supplied no domain), so `serviceUrl`
|
|
53
|
+
* falls back to the structured subdomain.
|
|
54
|
+
*
|
|
55
|
+
* Takes `unknown` because the callers reach it from four differently-typed
|
|
56
|
+
* router shapes (resolved state, the wire, a Home DB row, dev state); the
|
|
57
|
+
* shape check is here once rather than in each of them.
|
|
58
|
+
*/
|
|
59
|
+
export function firstPublicDomain(publicDomain: unknown): string | undefined {
|
|
60
|
+
if (publicDomain == null || typeof publicDomain !== "object") return undefined;
|
|
61
|
+
const hosts = (publicDomain as { hosts?: unknown }).hosts;
|
|
62
|
+
if (Array.isArray(hosts) && typeof hosts[0] === "string") return hosts[0];
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
package/src/service.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Ref } from "./ref.ts";
|
|
2
|
+
import { ref, stringRef } from "./ref.ts";
|
|
3
|
+
import type { ServiceDefinition } from "./service-definition.ts";
|
|
4
|
+
|
|
5
|
+
/** The placeholder appId that `derived()` emits for own-app derived secrets. */
|
|
6
|
+
export const SELF_APP_ID = "__self__";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Base class for service presets (Postgres, Valkey, Garage, etc.).
|
|
10
|
+
*/
|
|
11
|
+
export abstract class Service {
|
|
12
|
+
private _serviceKey: string | undefined;
|
|
13
|
+
|
|
14
|
+
/** Called by the framework after scanning the services map */
|
|
15
|
+
_setServiceKey(key: string) {
|
|
16
|
+
this._serviceKey = key;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A per-service derived secret. Within the same app, same-named secrets
|
|
21
|
+
* collide intentionally (Postgres.password and Valkey.password in the
|
|
22
|
+
* same app share a hash) - that's the ergonomic contract.
|
|
23
|
+
*/
|
|
24
|
+
protected derived(name: string): Ref<string> {
|
|
25
|
+
return ref({ kind: "derived", appId: SELF_APP_ID, name });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* DNS name of this service within the per-app compose project. Resolves
|
|
30
|
+
* to a plain string at serialization time - `_setServiceKey` is
|
|
31
|
+
* guaranteed to have run before `getServiceDefinition`, which is the
|
|
32
|
+
* only time a `host` ref is serialized.
|
|
33
|
+
*/
|
|
34
|
+
get host(): Ref<string> {
|
|
35
|
+
return stringRef(() => this._serviceKey ?? "__unresolved__");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
abstract getServiceDefinition(): ServiceDefinition;
|
|
39
|
+
}
|
package/src/shared.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Shared types for the permissions/inputs factory system.
|
|
2
|
+
//
|
|
3
|
+
// No runtime here — only kinds, mixin interfaces, and the Builder type
|
|
4
|
+
// machinery. All concrete factories (host.*, input.*, etc.) build on these.
|
|
5
|
+
|
|
6
|
+
import type { z } from "zod";
|
|
7
|
+
|
|
8
|
+
// ----- Kinds ---------------------------------------------------------------
|
|
9
|
+
//
|
|
10
|
+
// The closed `PermissionKind` (and its host/peer/remote subsets) is
|
|
11
|
+
// derived from the actual factory exports — see permissions.ts. It can't
|
|
12
|
+
// live here: the derivation walks schema/*, which depends on factory.ts,
|
|
13
|
+
// which imports this file — a type-level cycle. shared.ts carries only the
|
|
14
|
+
// inline shapes Builder uses.
|
|
15
|
+
|
|
16
|
+
export type InputKind =
|
|
17
|
+
| "input.text"
|
|
18
|
+
| "input.secret"
|
|
19
|
+
| "input.number"
|
|
20
|
+
| "input.boolean"
|
|
21
|
+
| "input.enum";
|
|
22
|
+
|
|
23
|
+
// ----- Factory shape -------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
export type Mode = "perm" | "input";
|
|
26
|
+
|
|
27
|
+
// Schema-aware factory shape. Zod's input/output split lets `.default(...)`
|
|
28
|
+
// schemas (input: T | undefined, output: T) keep the no-arg call form while
|
|
29
|
+
// data lands as the parsed output: the arg is optional when `undefined` is
|
|
30
|
+
// assignable to the schema's input, so `r.host.gpu()` typechecks but
|
|
31
|
+
// `r.host.port(80)` still requires the literal port.
|
|
32
|
+
export type FactoryFor<
|
|
33
|
+
S extends z.ZodType,
|
|
34
|
+
K,
|
|
35
|
+
BaseR extends object,
|
|
36
|
+
M extends Mode,
|
|
37
|
+
> = M extends "perm"
|
|
38
|
+
? (
|
|
39
|
+
...args: undefined extends z.input<S> ? [input?: z.input<S>] : [input: z.input<S>]
|
|
40
|
+
) => BaseR & { readonly kind: K; readonly data: z.output<S> }
|
|
41
|
+
: () => BaseR & { readonly kind: K; readonly schema: S };
|
|
42
|
+
|
|
43
|
+
export type Builder<BaseR extends object, M extends Mode, KindUnion extends string> = <
|
|
44
|
+
S extends z.ZodType,
|
|
45
|
+
K extends KindUnion,
|
|
46
|
+
>(
|
|
47
|
+
kind: K,
|
|
48
|
+
schema: S,
|
|
49
|
+
) => FactoryFor<S, K, BaseR, M>;
|
|
50
|
+
|
|
51
|
+
// ----- Slot helpers --------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
export type AddRequiredSlot<M, K extends string, V> = M & { readonly [P in K]: V };
|
|
54
|
+
export type AddOptionalSlot<M, K extends string, V> = M & { readonly [P in K]?: V };
|
|
55
|
+
|
|
56
|
+
// ----- Slot-absence gate ---------------------------------------------------
|
|
57
|
+
//
|
|
58
|
+
// Refuses double-stacking the same wrapper. If K is already in M, the
|
|
59
|
+
// rest-arg type is an impossible-to-satisfy [error: ...] tuple. If absent,
|
|
60
|
+
// rest args are []. Inference of BaseR happens via `inner: Builder<BaseR>`,
|
|
61
|
+
// not through this gate, so it stays clean.
|
|
62
|
+
|
|
63
|
+
export type GateNoSlot<M, K extends string> = K extends keyof M
|
|
64
|
+
? [error: `slot '${K & string}' already added — wrapper composed twice`]
|
|
65
|
+
: [];
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// The `host.silo` value a speculative eval sees when no Silo has been picked.
|
|
2
|
+
//
|
|
3
|
+
// The install preview grants every declared permission sight-unseen so the
|
|
4
|
+
// review step can show what an app would ship before the operator commits.
|
|
5
|
+
// `host.silo` is the one permission that cannot be granted that way: its
|
|
6
|
+
// descriptor carries the author's affordances (`{volume: "readwrite"}`) while
|
|
7
|
+
// its grant carries a Silo id the operator chooses, and there is no default
|
|
8
|
+
// Silo to fall back on. Without a stand-in, every app that joins a Silo drops
|
|
9
|
+
// out of its own preview and silently loses the whole Images panel.
|
|
10
|
+
//
|
|
11
|
+
// Lives in its own module rather than in `schema/host.ts` because `r.host` is
|
|
12
|
+
// built from that file's entire namespace (`index.ts`), so anything exported
|
|
13
|
+
// there becomes author-facing API - and this is the one value an author must
|
|
14
|
+
// never be able to mint.
|
|
15
|
+
//
|
|
16
|
+
// Nothing here reaches a worker: `evalApps` refuses `speculative` without
|
|
17
|
+
// `ephemeral`, and the two callers write no app row.
|
|
18
|
+
|
|
19
|
+
import { siloNetwork } from "./worker-protocol/networks.ts";
|
|
20
|
+
import type { SiloGrants, SiloHostPath, SiloResolved } from "./schema/host.ts";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The stand-in Silo's identity. Deliberately not plausible: nothing downstream
|
|
24
|
+
* of a speculative eval reads these, and if something ever starts to, they are
|
|
25
|
+
* legible things to find in the output rather than values someone could mistake
|
|
26
|
+
* for the operator's own.
|
|
27
|
+
*
|
|
28
|
+
* `containerPath` is the exception and matches `DEFAULT_CONTAINER_PATH` in
|
|
29
|
+
* Home's `silos.ts`, because an author who reads `volume.containerPath` into an
|
|
30
|
+
* env var should see the usual string.
|
|
31
|
+
*/
|
|
32
|
+
export const SPECULATIVE_SILO = {
|
|
33
|
+
id: "speculative",
|
|
34
|
+
hostPath: "/speculative-silo",
|
|
35
|
+
containerPath: "/silo",
|
|
36
|
+
/**
|
|
37
|
+
* The volume schema requires a positive gid, so this cannot be 0. Home draws
|
|
38
|
+
* real Silo gids from a counter starting at 1,000,000 (`home_state.next_gid`),
|
|
39
|
+
* so anything below that floor can never name a group an operator has.
|
|
40
|
+
*/
|
|
41
|
+
gid: 999_999,
|
|
42
|
+
/**
|
|
43
|
+
* Shape-faithful on purpose: 64 hex characters, the same as a derived key.
|
|
44
|
+
* A recognisable sentinel would look safer and behave worse - an app that
|
|
45
|
+
* validates its Silo key (`z.string().length(64)`, or a bare truthiness
|
|
46
|
+
* check) would drop out of its own preview, which is the exact failure this
|
|
47
|
+
* whole module exists to prevent.
|
|
48
|
+
*/
|
|
49
|
+
apiKey: "0".repeat(64),
|
|
50
|
+
} as const;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Build the resolved value from the author's declaration alone.
|
|
54
|
+
*
|
|
55
|
+
* Everything the preview needs - which images, which services, whether
|
|
56
|
+
* anything mounts a volume - falls out of the declared shape. Which directory
|
|
57
|
+
* the bytes actually live in changes none of it.
|
|
58
|
+
*
|
|
59
|
+
* The require-map is answered from the ask rather than from the Silo's service
|
|
60
|
+
* directory, and the reason is structural rather than a judgement call: Home's
|
|
61
|
+
* preview evaluates the candidate on its own (`evalApps([candidate])`), so the
|
|
62
|
+
* directory is merged from an empty set and comes back empty for every Silo id.
|
|
63
|
+
* A require-map in preview is therefore unmet unconditionally - whatever the
|
|
64
|
+
* operator has installed, whichever Silo they would go on to pick. Answering
|
|
65
|
+
* from the ask declines to emit a signal that is always false. The real check
|
|
66
|
+
* still runs at install, where `speculative` is off and the operator has
|
|
67
|
+
* actually chosen a Silo, and it still produces `siloRequiresUnmetMessage`.
|
|
68
|
+
*/
|
|
69
|
+
export function speculativeSilo(asked: SiloGrants): SiloResolved {
|
|
70
|
+
const resolved: SiloResolved = {};
|
|
71
|
+
if (asked.network !== undefined) {
|
|
72
|
+
(resolved as { network: SiloResolved["network"] }).network = siloNetwork(SPECULATIVE_SILO.id);
|
|
73
|
+
if (asked.network !== true) {
|
|
74
|
+
const services: Record<string, { host: string; port: number }> = {};
|
|
75
|
+
for (const [name, port] of Object.entries(asked.network)) {
|
|
76
|
+
services[name] = { host: name, port };
|
|
77
|
+
}
|
|
78
|
+
(resolved as { services: SiloResolved["services"] }).services = services;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (asked.volume !== undefined) {
|
|
82
|
+
(resolved as { volume: SiloResolved["volume"] }).volume = {
|
|
83
|
+
hostPath: SPECULATIVE_SILO.hostPath as SiloHostPath,
|
|
84
|
+
containerPath: SPECULATIVE_SILO.containerPath,
|
|
85
|
+
gid: SPECULATIVE_SILO.gid,
|
|
86
|
+
// The author declared the mode and the placeholder honours it: the shape
|
|
87
|
+
// an app sees must not change with the mode of the eval.
|
|
88
|
+
readOnly: asked.volume === "read",
|
|
89
|
+
backup: false,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (asked.apiKey !== undefined) {
|
|
93
|
+
(resolved as { apiKey: SiloResolved["apiKey"] }).apiKey = SPECULATIVE_SILO.apiKey;
|
|
94
|
+
}
|
|
95
|
+
return resolved;
|
|
96
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Ref } from "./ref.ts";
|
|
2
|
+
import { ref } from "./ref.ts";
|
|
3
|
+
import { SELF_APP_ID } from "./service.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* App-scoped stable secret ref. Same name + same app yields the same
|
|
7
|
+
* value across deploys; different apps yield different values for the
|
|
8
|
+
* same name. Home resolves these via HMAC over
|
|
9
|
+
* `JSON(["derived-password", appId, name])` using a seed derived from
|
|
10
|
+
* `REALM_SECRET` (see `platform/home/src/server/keys.ts`).
|
|
11
|
+
*/
|
|
12
|
+
export function stableSecret(name: string): Ref<string> {
|
|
13
|
+
return ref({ kind: "derived", appId: SELF_APP_ID, name });
|
|
14
|
+
}
|