@alchemy.run/archil 0.0.0 → 2.0.0-beta.87
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 +201 -0
- package/README.md +57 -4
- package/lib/AWS/S3Disk.d.ts +68 -0
- package/lib/AWS/S3Disk.d.ts.map +1 -0
- package/lib/AWS/S3Disk.js +100 -0
- package/lib/AWS/S3Disk.js.map +1 -0
- package/lib/AWS/index.d.ts +2 -0
- package/lib/AWS/index.d.ts.map +1 -0
- package/lib/AWS/index.js +2 -0
- package/lib/AWS/index.js.map +1 -0
- package/lib/ApiToken.d.ts +63 -0
- package/lib/ApiToken.d.ts.map +1 -0
- package/lib/ApiToken.js +121 -0
- package/lib/ApiToken.js.map +1 -0
- package/lib/ArchilBindings.d.ts +65 -0
- package/lib/ArchilBindings.d.ts.map +1 -0
- package/lib/ArchilBindings.js +128 -0
- package/lib/ArchilBindings.js.map +1 -0
- package/lib/AuthProvider.d.ts +40 -0
- package/lib/AuthProvider.d.ts.map +1 -0
- package/lib/AuthProvider.js +87 -0
- package/lib/AuthProvider.js.map +1 -0
- package/lib/Bootstrap.d.ts +8 -0
- package/lib/Bootstrap.d.ts.map +1 -0
- package/lib/Bootstrap.js +27 -0
- package/lib/Bootstrap.js.map +1 -0
- package/lib/Branch.d.ts +101 -0
- package/lib/Branch.d.ts.map +1 -0
- package/lib/Branch.js +117 -0
- package/lib/Branch.js.map +1 -0
- package/lib/BranchDisk.d.ts +42 -0
- package/lib/BranchDisk.d.ts.map +1 -0
- package/lib/BranchDisk.js +14 -0
- package/lib/BranchDisk.js.map +1 -0
- package/lib/Checkpoint.d.ts +78 -0
- package/lib/Checkpoint.d.ts.map +1 -0
- package/lib/Checkpoint.js +95 -0
- package/lib/Checkpoint.js.map +1 -0
- package/lib/Clients.d.ts +369 -0
- package/lib/Clients.d.ts.map +1 -0
- package/lib/Clients.js +334 -0
- package/lib/Clients.js.map +1 -0
- package/lib/ConnectSandbox.d.ts +69 -0
- package/lib/ConnectSandbox.d.ts.map +1 -0
- package/lib/ConnectSandbox.js +21 -0
- package/lib/ConnectSandbox.js.map +1 -0
- package/lib/Contributions.d.ts +7 -0
- package/lib/Contributions.d.ts.map +1 -0
- package/lib/Contributions.js +6 -0
- package/lib/Contributions.js.map +1 -0
- package/lib/CreateDisk.d.ts +90 -0
- package/lib/CreateDisk.d.ts.map +1 -0
- package/lib/CreateDisk.js +47 -0
- package/lib/CreateDisk.js.map +1 -0
- package/lib/CreateImage.d.ts +42 -0
- package/lib/CreateImage.d.ts.map +1 -0
- package/lib/CreateImage.js +19 -0
- package/lib/CreateImage.js.map +1 -0
- package/lib/CreateSandbox.d.ts +104 -0
- package/lib/CreateSandbox.d.ts.map +1 -0
- package/lib/CreateSandbox.js +98 -0
- package/lib/CreateSandbox.js.map +1 -0
- package/lib/Credentials.d.ts +44 -0
- package/lib/Credentials.d.ts.map +1 -0
- package/lib/Credentials.js +57 -0
- package/lib/Credentials.js.map +1 -0
- package/lib/Disk.d.ts +283 -0
- package/lib/Disk.d.ts.map +1 -0
- package/lib/Disk.js +294 -0
- package/lib/Disk.js.map +1 -0
- package/lib/DiskObjectsHttp.d.ts +20 -0
- package/lib/DiskObjectsHttp.d.ts.map +1 -0
- package/lib/DiskObjectsHttp.js +51 -0
- package/lib/DiskObjectsHttp.js.map +1 -0
- package/lib/DiskUser.d.ts +189 -0
- package/lib/DiskUser.d.ts.map +1 -0
- package/lib/DiskUser.js +243 -0
- package/lib/DiskUser.js.map +1 -0
- package/lib/ExecDisk.d.ts +57 -0
- package/lib/ExecDisk.d.ts.map +1 -0
- package/lib/ExecDisk.js +3 -0
- package/lib/ExecDisk.js.map +1 -0
- package/lib/ExecDiskHttp.d.ts +5 -0
- package/lib/ExecDiskHttp.d.ts.map +1 -0
- package/lib/ExecDiskHttp.js +18 -0
- package/lib/ExecDiskHttp.js.map +1 -0
- package/lib/ExecDisks.d.ts +54 -0
- package/lib/ExecDisks.d.ts.map +1 -0
- package/lib/ExecDisks.js +37 -0
- package/lib/ExecDisks.js.map +1 -0
- package/lib/Image.d.ts +188 -0
- package/lib/Image.d.ts.map +1 -0
- package/lib/Image.js +191 -0
- package/lib/Image.js.map +1 -0
- package/lib/InferEnv.d.ts +26 -0
- package/lib/InferEnv.d.ts.map +1 -0
- package/lib/InferEnv.js +1 -0
- package/lib/InferEnv.js.map +1 -0
- package/lib/MountDisk.d.ts +190 -0
- package/lib/MountDisk.d.ts.map +1 -0
- package/lib/MountDisk.js +227 -0
- package/lib/MountDisk.js.map +1 -0
- package/lib/Providers.d.ts +25 -0
- package/lib/Providers.d.ts.map +1 -0
- package/lib/Providers.js +48 -0
- package/lib/Providers.js.map +1 -0
- package/lib/ReadDisk.d.ts +55 -0
- package/lib/ReadDisk.d.ts.map +1 -0
- package/lib/ReadDisk.js +3 -0
- package/lib/ReadDisk.js.map +1 -0
- package/lib/ReadWriteDisk.d.ts +26 -0
- package/lib/ReadWriteDisk.d.ts.map +1 -0
- package/lib/ReadWriteDisk.js +3 -0
- package/lib/ReadWriteDisk.js.map +1 -0
- package/lib/Sandbox.d.ts +507 -0
- package/lib/Sandbox.d.ts.map +1 -0
- package/lib/Sandbox.js +729 -0
- package/lib/Sandbox.js.map +1 -0
- package/lib/SandboxOptions.d.ts +185 -0
- package/lib/SandboxOptions.d.ts.map +1 -0
- package/lib/SandboxOptions.js +102 -0
- package/lib/SandboxOptions.js.map +1 -0
- package/lib/SandboxPortToken.d.ts +64 -0
- package/lib/SandboxPortToken.d.ts.map +1 -0
- package/lib/SandboxPortToken.js +77 -0
- package/lib/SandboxPortToken.js.map +1 -0
- package/lib/WriteDisk.d.ts +37 -0
- package/lib/WriteDisk.d.ts.map +1 -0
- package/lib/WriteDisk.js +3 -0
- package/lib/WriteDisk.js.map +1 -0
- package/lib/index.d.ts +29 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +28 -0
- package/lib/index.js.map +1 -0
- package/lib/internal/DeployerKey.d.ts +21 -0
- package/lib/internal/DeployerKey.d.ts.map +1 -0
- package/lib/internal/DeployerKey.js +34 -0
- package/lib/internal/DeployerKey.js.map +1 -0
- package/lib/internal/archil.d.ts +87 -0
- package/lib/internal/archil.d.ts.map +1 -0
- package/lib/internal/archil.js +154 -0
- package/lib/internal/archil.js.map +1 -0
- package/lib/internal/hosted.d.ts +90 -0
- package/lib/internal/hosted.d.ts.map +1 -0
- package/lib/internal/hosted.js +291 -0
- package/lib/internal/hosted.js.map +1 -0
- package/package.json +79 -3
- package/src/AWS/S3Disk.ts +125 -0
- package/src/AWS/index.ts +1 -0
- package/src/ApiToken.ts +168 -0
- package/src/ArchilBindings.ts +179 -0
- package/src/AuthProvider.ts +122 -0
- package/src/Bootstrap.ts +54 -0
- package/src/Branch.ts +199 -0
- package/src/BranchDisk.ts +57 -0
- package/src/Checkpoint.ts +145 -0
- package/src/Clients.ts +954 -0
- package/src/ConnectSandbox.ts +91 -0
- package/src/Contributions.ts +9 -0
- package/src/CreateDisk.ts +165 -0
- package/src/CreateImage.ts +73 -0
- package/src/CreateSandbox.ts +258 -0
- package/src/Credentials.ts +101 -0
- package/src/Disk.ts +468 -0
- package/src/DiskObjectsHttp.ts +64 -0
- package/src/DiskUser.ts +369 -0
- package/src/ExecDisk.ts +62 -0
- package/src/ExecDiskHttp.ts +25 -0
- package/src/ExecDisks.ts +100 -0
- package/src/Image.ts +316 -0
- package/src/InferEnv.ts +27 -0
- package/src/MountDisk.ts +466 -0
- package/src/Providers.ts +75 -0
- package/src/ReadDisk.ts +61 -0
- package/src/ReadWriteDisk.ts +31 -0
- package/src/Sandbox.ts +1064 -0
- package/src/SandboxOptions.ts +230 -0
- package/src/SandboxPortToken.ts +125 -0
- package/src/WriteDisk.ts +42 -0
- package/src/globals.d.ts +2 -0
- package/src/index.ts +59 -0
- package/src/internal/DeployerKey.ts +63 -0
- package/src/internal/archil.ts +273 -0
- package/src/internal/hosted.ts +402 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundling and deploying the program of an `Archil.Sandbox` with `main`: a
|
|
3
|
+
* Node process run as an Archil sandbox service (`archil-sandbox services
|
|
4
|
+
* create`), which restarts when it crashes and starts whenever the sandbox
|
|
5
|
+
* boots.
|
|
6
|
+
*/
|
|
7
|
+
import * as archil from "@distilled.cloud/archil";
|
|
8
|
+
import * as Process from "@distilled.cloud/archil/Process";
|
|
9
|
+
import * as Bundle from "alchemy/Bundle/Bundle";
|
|
10
|
+
import { normalizeInstallTargets, resolvePackageInstallIdentity, } from "alchemy/Bundle/InstalledPackages";
|
|
11
|
+
import { findCwdForBundle, resolveMainPath } from "alchemy/Bundle/TempRoot";
|
|
12
|
+
import { contextRootOf, extraFileDestination, hashExtraFiles, isContextRootDest, posixRelUnder, resolveExtraSource, } from "alchemy/Util/extraFiles";
|
|
13
|
+
import { sha256, sha256Object } from "alchemy/Util/sha256";
|
|
14
|
+
import { zipFiles } from "alchemy/Util/zip";
|
|
15
|
+
import * as Data from "effect/Data";
|
|
16
|
+
import * as Effect from "effect/Effect";
|
|
17
|
+
import * as FileSystem from "effect/FileSystem";
|
|
18
|
+
import * as Path from "effect/Path";
|
|
19
|
+
import * as Redacted from "effect/Redacted";
|
|
20
|
+
import * as Schedule from "effect/Schedule";
|
|
21
|
+
import { waitForSandbox } from "./archil.js";
|
|
22
|
+
/** A sandbox program failed to deploy or never started serving. */
|
|
23
|
+
export class ProgramDeployFailed extends Data.TaggedError("Archil.ProgramDeployFailed") {
|
|
24
|
+
}
|
|
25
|
+
/** Entry for the Effect form: the package bootstrap plus the user's `main`. */
|
|
26
|
+
const effectEntry = (handler) => (importPath) => `
|
|
27
|
+
import { bootstrap } from "@alchemy.run/archil/Bootstrap";
|
|
28
|
+
import { ${handler} as entrypoint } from ${JSON.stringify(importPath)};
|
|
29
|
+
|
|
30
|
+
await bootstrap(entrypoint);
|
|
31
|
+
`;
|
|
32
|
+
/**
|
|
33
|
+
* Entry for the async form: serve the module's `fetch(request, env)` over
|
|
34
|
+
* `node:http`, with `process.env` as `env`. Nothing from alchemy or Effect
|
|
35
|
+
* is bundled.
|
|
36
|
+
*/
|
|
37
|
+
const asyncEntry = (handler) => (importPath) => `
|
|
38
|
+
import * as mod from ${JSON.stringify(importPath)};
|
|
39
|
+
import http from "node:http";
|
|
40
|
+
import { Readable } from "node:stream";
|
|
41
|
+
|
|
42
|
+
const app = mod[${JSON.stringify(handler)}] ?? mod.default;
|
|
43
|
+
const port = Number(process.env.PORT ?? 3000);
|
|
44
|
+
|
|
45
|
+
http
|
|
46
|
+
.createServer(async (req, res) => {
|
|
47
|
+
try {
|
|
48
|
+
const url = new URL(req.url ?? "/", \`http://\${req.headers.host ?? "localhost"}\`);
|
|
49
|
+
const headers = new Headers();
|
|
50
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
51
|
+
for (const v of Array.isArray(value) ? value : value === undefined ? [] : [value]) {
|
|
52
|
+
headers.append(key, v);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const hasBody = req.method !== "GET" && req.method !== "HEAD";
|
|
56
|
+
const request = new Request(url, {
|
|
57
|
+
method: req.method,
|
|
58
|
+
headers,
|
|
59
|
+
body: hasBody ? Readable.toWeb(req) : undefined,
|
|
60
|
+
duplex: hasBody ? "half" : undefined,
|
|
61
|
+
});
|
|
62
|
+
const response = await app.fetch(request, process.env);
|
|
63
|
+
res.writeHead(response.status, Object.fromEntries(response.headers));
|
|
64
|
+
if (response.body) Readable.fromWeb(response.body).pipe(res);
|
|
65
|
+
else res.end();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(error);
|
|
68
|
+
if (!res.headersSent) res.writeHead(500);
|
|
69
|
+
res.end("Internal Server Error");
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
.listen(port, "0.0.0.0");
|
|
73
|
+
`;
|
|
74
|
+
/**
|
|
75
|
+
* Paths left out of an upload. A tree uploaded at the program root keeps
|
|
76
|
+
* its `node_modules` (framework servers like Nuxt trace their dependencies
|
|
77
|
+
* there); other trees drop them.
|
|
78
|
+
*/
|
|
79
|
+
const skipped = (relative, atRoot) => relative
|
|
80
|
+
.replaceAll("\\", "/")
|
|
81
|
+
.split("/")
|
|
82
|
+
.some((segment) => segment === ".git" || (!atRoot && segment === "node_modules"));
|
|
83
|
+
/** Read every extra file into program files at their destinations. */
|
|
84
|
+
const collectExtraFiles = Effect.fn(function* (extraFiles) {
|
|
85
|
+
const fs = yield* FileSystem.FileSystem;
|
|
86
|
+
const path = yield* Path.Path;
|
|
87
|
+
const files = [];
|
|
88
|
+
for (const extra of extraFiles) {
|
|
89
|
+
const dest = extraFileDestination(extra.destination);
|
|
90
|
+
const source = resolveExtraSource(extra.source, path);
|
|
91
|
+
if (!(yield* fs.exists(source)))
|
|
92
|
+
continue;
|
|
93
|
+
if ((yield* fs.stat(source)).type === "File") {
|
|
94
|
+
files.push({
|
|
95
|
+
path: isContextRootDest(dest) ? path.basename(source) : dest,
|
|
96
|
+
content: yield* fs.readFile(source),
|
|
97
|
+
});
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const atRoot = isContextRootDest(dest);
|
|
101
|
+
for (const name of yield* fs.readDirectory(source, { recursive: true })) {
|
|
102
|
+
if (skipped(name, atRoot))
|
|
103
|
+
continue;
|
|
104
|
+
const full = path.join(source, name);
|
|
105
|
+
if ((yield* fs.stat(full)).type !== "File")
|
|
106
|
+
continue;
|
|
107
|
+
const rel = name.replaceAll("\\", "/");
|
|
108
|
+
files.push({
|
|
109
|
+
path: isContextRootDest(dest) ? rel : `${dest}/${rel}`.replaceAll(/\/+/g, "/"),
|
|
110
|
+
content: yield* fs.readFile(full),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return files;
|
|
115
|
+
});
|
|
116
|
+
/** The `package.json` that installs `build.install`, pinned to the versions the project resolves. */
|
|
117
|
+
const installManifest = Effect.fn(function* (cwd, install) {
|
|
118
|
+
const requested = yield* normalizeInstallTargets(install);
|
|
119
|
+
if (Object.keys(requested).length === 0)
|
|
120
|
+
return undefined;
|
|
121
|
+
const identity = yield* resolvePackageInstallIdentity({ cwd, requested });
|
|
122
|
+
if (Object.keys(identity.resolved).length === 0)
|
|
123
|
+
return undefined;
|
|
124
|
+
return `${JSON.stringify({ private: true, type: "module", dependencies: identity.resolved }, null, 2)}\n`;
|
|
125
|
+
});
|
|
126
|
+
/**
|
|
127
|
+
* Pack `main` into the files of a Node program.
|
|
128
|
+
*
|
|
129
|
+
* - The Effect form bundles `main` behind the package bootstrap.
|
|
130
|
+
* - The async form bundles `main` behind a `node:http` server for its
|
|
131
|
+
* `fetch(request, env)`.
|
|
132
|
+
* - With `extraFiles`, `main` is already a complete Node program (a
|
|
133
|
+
* framework's server entry): it is uploaded as built, with its tree.
|
|
134
|
+
*/
|
|
135
|
+
export const bundleProgram = Effect.fn(function* (props, virtualEntryPlugin) {
|
|
136
|
+
const handler = props.handler ?? "default";
|
|
137
|
+
const realMain = yield* resolveMainPath(props.main);
|
|
138
|
+
const cwd = yield* findCwdForBundle(realMain);
|
|
139
|
+
const manifest = yield* installManifest(cwd, props.build?.install);
|
|
140
|
+
const manifestFiles = manifest === undefined
|
|
141
|
+
? []
|
|
142
|
+
: [{ path: "package.json", content: new TextEncoder().encode(manifest) }];
|
|
143
|
+
if (props.isExternal && props.extraFiles !== undefined) {
|
|
144
|
+
const fs = yield* FileSystem.FileSystem;
|
|
145
|
+
const path = yield* Path.Path;
|
|
146
|
+
const extras = props.extraFiles.map((f) => ({ source: f.source, dest: f.destination }));
|
|
147
|
+
const root = contextRootOf(realMain, extras, path, (source) => resolveExtraSource(source, path));
|
|
148
|
+
const entryRel = posixRelUnder(root, realMain, path) ?? path.basename(realMain);
|
|
149
|
+
const entry = yield* fs.readFile(realMain);
|
|
150
|
+
const files = [
|
|
151
|
+
...(yield* collectExtraFiles(props.extraFiles)).filter((f) => !(f.path === "package.json" && manifest !== undefined)),
|
|
152
|
+
...manifestFiles,
|
|
153
|
+
];
|
|
154
|
+
if (!files.some((f) => f.path === entryRel))
|
|
155
|
+
files.push({ path: entryRel, content: entry });
|
|
156
|
+
const hash = yield* sha256Object({
|
|
157
|
+
entry: yield* sha256(entry),
|
|
158
|
+
extra: yield* hashExtraFiles(extras),
|
|
159
|
+
manifest: manifest ?? "",
|
|
160
|
+
entryRel,
|
|
161
|
+
});
|
|
162
|
+
return { files, hash, entryRel, install: manifest !== undefined };
|
|
163
|
+
}
|
|
164
|
+
const entry = props.isExternal ? asyncEntry(handler) : effectEntry(handler);
|
|
165
|
+
const output = yield* Bundle.build({
|
|
166
|
+
...props.build?.input,
|
|
167
|
+
input: realMain,
|
|
168
|
+
cwd,
|
|
169
|
+
platform: "node",
|
|
170
|
+
resolve: {
|
|
171
|
+
conditionNames: [...Bundle.NODE_CONDITION_NAMES],
|
|
172
|
+
...props.build?.input?.resolve,
|
|
173
|
+
},
|
|
174
|
+
plugins: [props.build?.input?.plugins, virtualEntryPlugin(entry)],
|
|
175
|
+
}, {
|
|
176
|
+
...props.build?.output,
|
|
177
|
+
format: "esm",
|
|
178
|
+
sourcemap: props.build?.output?.sourcemap ?? false,
|
|
179
|
+
minify: props.build?.output?.minify ?? false,
|
|
180
|
+
entryFileNames: "index.mjs",
|
|
181
|
+
strictExecutionOrder: true,
|
|
182
|
+
keepNames: true,
|
|
183
|
+
}, props.build);
|
|
184
|
+
const files = [
|
|
185
|
+
...output.files.map((file) => ({
|
|
186
|
+
path: file.path,
|
|
187
|
+
content: typeof file.content === "string" ? new TextEncoder().encode(file.content) : file.content,
|
|
188
|
+
})),
|
|
189
|
+
...manifestFiles,
|
|
190
|
+
];
|
|
191
|
+
const hash = yield* sha256Object({ bundle: output.hash, manifest: manifest ?? "" });
|
|
192
|
+
return {
|
|
193
|
+
files,
|
|
194
|
+
hash,
|
|
195
|
+
entryRel: "index.mjs",
|
|
196
|
+
install: manifest !== undefined,
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
/** Single-quote a value for `sh`. */
|
|
200
|
+
const shellQuote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
201
|
+
const envValue = (value) => Redacted.isRedacted(value)
|
|
202
|
+
? String(Redacted.value(value))
|
|
203
|
+
: typeof value === "string"
|
|
204
|
+
? value
|
|
205
|
+
: JSON.stringify(value ?? null);
|
|
206
|
+
/** Boot a sandbox that is paused or stopped, and wait until it runs. */
|
|
207
|
+
export const ensureRunning = Effect.fn(function* (sid) {
|
|
208
|
+
const sandbox = yield* waitForSandbox(sid);
|
|
209
|
+
if (sandbox.status === "paused") {
|
|
210
|
+
yield* archil
|
|
211
|
+
.resumeSandbox({ sid })
|
|
212
|
+
.pipe(Effect.catchTag("SandboxStartConflict", () => Effect.void));
|
|
213
|
+
}
|
|
214
|
+
else if (sandbox.status === "stopped" || sandbox.status === "exited") {
|
|
215
|
+
yield* archil
|
|
216
|
+
.startSandbox({ sid })
|
|
217
|
+
.pipe(Effect.catchTag("SandboxStartConflict", () => Effect.void));
|
|
218
|
+
}
|
|
219
|
+
const ready = yield* waitForSandbox(sid);
|
|
220
|
+
if (ready.status !== "running") {
|
|
221
|
+
return yield* new ProgramDeployFailed({
|
|
222
|
+
program: sid,
|
|
223
|
+
message: `Sandbox ${ready.name} is ${ready.status}, not running`,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
/** Run a shell command in the sandbox and fail with its output on a non-zero exit. */
|
|
228
|
+
const sh = Effect.fn(function* (sid, program, command) {
|
|
229
|
+
const result = yield* Process.exec(sid, command);
|
|
230
|
+
if (result.exitCode !== 0) {
|
|
231
|
+
return yield* new ProgramDeployFailed({
|
|
232
|
+
program,
|
|
233
|
+
message: `\`${command.split("\n")[0]}\` exited ${result.exitCode ?? result.status}: ${(result.stderr || result.stdout).trim()}`,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return result.stdout;
|
|
237
|
+
});
|
|
238
|
+
/** The directory a program lives in. */
|
|
239
|
+
export const programDir = (name) => `/opt/alchemy/${name}`;
|
|
240
|
+
/**
|
|
241
|
+
* Upload the program, (re)define its service, and wait until it answers on
|
|
242
|
+
* `port` inside the sandbox.
|
|
243
|
+
*/
|
|
244
|
+
export const deployProgram = Effect.fn(function* (input) {
|
|
245
|
+
const dir = programDir(input.name);
|
|
246
|
+
const staging = `${dir}.next`;
|
|
247
|
+
yield* ensureRunning(input.sid);
|
|
248
|
+
yield* sh(input.sid, input.name, `rm -rf ${shellQuote(staging)} && mkdir -p ${shellQuote(staging)}`);
|
|
249
|
+
// One archive, one upload: a framework build can have thousands of files.
|
|
250
|
+
const archive = yield* zipFiles(input.program.files);
|
|
251
|
+
yield* Effect.logInfo(`Archil.Sandbox program: uploading ${input.program.files.length} files (${Math.round(archive.byteLength / 1024)} KiB)`);
|
|
252
|
+
yield* Process.uploadFile(input.sid, `${staging}.zip`, archive);
|
|
253
|
+
yield* sh(input.sid, input.name, `python3 -m zipfile -e ${shellQuote(`${staging}.zip`)} ${shellQuote(staging)} && rm -f ${shellQuote(`${staging}.zip`)}`);
|
|
254
|
+
if (input.program.install) {
|
|
255
|
+
yield* Effect.logInfo(`Archil.Sandbox program: npm install`);
|
|
256
|
+
yield* sh(input.sid, input.name, `cd ${shellQuote(staging)} && npm install --omit=dev --no-fund --no-audit --loglevel=error`);
|
|
257
|
+
}
|
|
258
|
+
const envFlags = Object.entries({ ...input.env, PORT: String(input.port) })
|
|
259
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
260
|
+
.map(([key, value]) => `--env ${shellQuote(`${key}=${envValue(value)}`)}`)
|
|
261
|
+
.join(" ");
|
|
262
|
+
yield* sh(input.sid, input.name, [
|
|
263
|
+
`archil-sandbox services delete ${shellQuote(input.name)} >/dev/null 2>&1 || true`,
|
|
264
|
+
`rm -rf ${shellQuote(dir)} && mv ${shellQuote(staging)} ${shellQuote(dir)}`,
|
|
265
|
+
`archil-sandbox services create ${shellQuote(input.name)} --tcp-port ${input.port} --dir ${shellQuote(dir)} ${envFlags} -- node ${shellQuote(input.program.entryRel)} >/dev/null`,
|
|
266
|
+
].join(" && "));
|
|
267
|
+
yield* Effect.logInfo(`Archil.Sandbox program: started, waiting for port ${input.port}`);
|
|
268
|
+
// Wait until the process answers HTTP on its port.
|
|
269
|
+
const probe = `curl -s -o /dev/null -m 2 -w '%{http_code}' http://127.0.0.1:${input.port}/ || true`;
|
|
270
|
+
const code = yield* Process.exec(input.sid, probe).pipe(Effect.map((r) => r.stdout.trim()), Effect.repeat({
|
|
271
|
+
schedule: Schedule.spaced("1 second"),
|
|
272
|
+
until: (status) => status !== "" && status !== "000",
|
|
273
|
+
times: 30,
|
|
274
|
+
}));
|
|
275
|
+
if (code === "" || code === "000") {
|
|
276
|
+
const logs = yield* Process.exec(input.sid, `archil-sandbox services logs ${shellQuote(input.name)} 2>&1 | tail -n 40`).pipe(Effect.map((r) => r.stdout), Effect.orElseSucceed(() => ""));
|
|
277
|
+
return yield* new ProgramDeployFailed({
|
|
278
|
+
program: input.name,
|
|
279
|
+
message: `The program never answered on port ${input.port}.\n${logs}`,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
/** Stop the program and remove its files. Removing a missing one succeeds. */
|
|
284
|
+
export const removeProgram = Effect.fn(function* (sid, name) {
|
|
285
|
+
const sandbox = yield* waitForSandbox(sid).pipe(Effect.catchTag("NotFound", () => Effect.undefined));
|
|
286
|
+
if (sandbox === undefined || sandbox.status === "deleted")
|
|
287
|
+
return;
|
|
288
|
+
yield* ensureRunning(sid);
|
|
289
|
+
yield* Process.exec(sid, `archil-sandbox services delete ${shellQuote(name)} >/dev/null 2>&1; rm -rf ${shellQuote(programDir(name))}`);
|
|
290
|
+
});
|
|
291
|
+
//# sourceMappingURL=hosted.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hosted.js","sources":["../../src/internal/hosted.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alchemy.run/archil",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "2.0.0-beta.87",
|
|
4
4
|
"description": "Archil disks, sandboxes, images and mounts for Alchemy.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"alchemy",
|
|
@@ -10,12 +10,88 @@
|
|
|
10
10
|
"infrastructure-as-code",
|
|
11
11
|
"sandbox"
|
|
12
12
|
],
|
|
13
|
+
"homepage": "https://alchemy.run",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/alchemy-run/alchemy/issues"
|
|
16
|
+
},
|
|
13
17
|
"license": "Apache-2.0",
|
|
14
18
|
"author": "Sam Goodwin <sam@alchemy.run>",
|
|
15
|
-
"homepage": "https://alchemy.run",
|
|
16
19
|
"repository": {
|
|
17
20
|
"type": "git",
|
|
18
21
|
"url": "git+https://github.com/alchemy-run/alchemy.git",
|
|
19
22
|
"directory": "packages/archil"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"lib",
|
|
26
|
+
"src"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"sideEffects": false,
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./lib/index.d.ts",
|
|
33
|
+
"bun": "./src/index.ts",
|
|
34
|
+
"worker": "./src/index.ts",
|
|
35
|
+
"default": "./lib/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./Bootstrap": {
|
|
38
|
+
"types": "./src/Bootstrap.ts",
|
|
39
|
+
"bun": "./src/Bootstrap.ts",
|
|
40
|
+
"default": "./src/Bootstrap.ts"
|
|
41
|
+
},
|
|
42
|
+
"./AWS": {
|
|
43
|
+
"types": "./lib/AWS/index.d.ts",
|
|
44
|
+
"bun": "./src/AWS/index.ts",
|
|
45
|
+
"worker": "./src/AWS/index.ts",
|
|
46
|
+
"default": "./lib/AWS/index.js"
|
|
47
|
+
},
|
|
48
|
+
"./internal/*": null,
|
|
49
|
+
"./*": {
|
|
50
|
+
"types": "./lib/*.d.ts",
|
|
51
|
+
"bun": "./src/*.ts",
|
|
52
|
+
"worker": "./src/*.ts",
|
|
53
|
+
"default": "./lib/*.js"
|
|
54
|
+
},
|
|
55
|
+
"./package.json": "./package.json"
|
|
56
|
+
},
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"access": "public"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@alchemy.run/docker": "2.0.0-beta.87",
|
|
62
|
+
"@distilled.cloud/archil": "1.0.0-rc.20",
|
|
63
|
+
"@distilled.cloud/core": "1.0.0-rc.20",
|
|
64
|
+
"rolldown": "1.2.16"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@alchemy.run/aws": "2.0.0-beta.87",
|
|
68
|
+
"@effect/platform-node": "^4.1.4",
|
|
69
|
+
"@types/bun": "1.4.3",
|
|
70
|
+
"alchemy": "2.0.0-beta.87",
|
|
71
|
+
"effect": "^4.1.4",
|
|
72
|
+
"typescript": "^7.1.0-dev.20261105.1"
|
|
73
|
+
},
|
|
74
|
+
"peerDependencies": {
|
|
75
|
+
"@alchemy.run/aws": "2.0.0-beta.87",
|
|
76
|
+
"@effect/platform-node": "^4.1.4",
|
|
77
|
+
"alchemy": "2.0.0-beta.87",
|
|
78
|
+
"effect": "^4.1.4"
|
|
79
|
+
},
|
|
80
|
+
"peerDependenciesMeta": {
|
|
81
|
+
"@alchemy.run/aws": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"@effect/platform-node": {
|
|
85
|
+
"optional": true
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"alchemy": {
|
|
89
|
+
"contributes": [
|
|
90
|
+
"@alchemy.run/archil/Contributions"
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
"scripts": {
|
|
94
|
+
"build": "pnpm -r --filter \"{.}...\" run build:package",
|
|
95
|
+
"build:package": "node scripts/build.ts"
|
|
20
96
|
}
|
|
21
|
-
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { PolicyStatement } from "@alchemy.run/aws/IAM/Policy";
|
|
2
|
+
import type * as S3 from "@alchemy.run/aws/S3";
|
|
3
|
+
import * as Output from "alchemy/Output";
|
|
4
|
+
import * as Data from "effect/Data";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import { account } from "../Credentials.ts";
|
|
7
|
+
import { Disk, type DiskProps } from "../Disk.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The IAM role Archil assumes to reach your bucket, per Archil region.
|
|
11
|
+
* @see https://docs.archil.com/data-sources/s3-object-storage
|
|
12
|
+
*/
|
|
13
|
+
export const ARCHIL_S3_PRINCIPALS: Record<string, string> = {
|
|
14
|
+
"aws-us-east-1": "arn:aws:iam::904233114299:role/archil-s3.prod.us-east-1",
|
|
15
|
+
"aws-us-east-2": "arn:aws:iam::787372716928:role/archil-s3.prod.aws.us-east-2",
|
|
16
|
+
"aws-us-west-2": "arn:aws:iam::795289710871:role/archil-s3.prod.aws.us-west-2",
|
|
17
|
+
"aws-eu-west-1": "arn:aws:iam::794252461707:role/archil-s3.prod.aws.eu-west-1",
|
|
18
|
+
"gcp-us-central1": "arn:aws:iam::025016447135:role/archil-s3.prod.gcp.us-central1",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** The Archil organization isn't configured, so the bucket policy can't be scoped to it. */
|
|
22
|
+
export class ArchilOrganizationRequired extends Data.TaggedError("Archil.OrganizationRequired")<{
|
|
23
|
+
readonly message: string;
|
|
24
|
+
}> {}
|
|
25
|
+
|
|
26
|
+
/** An Archil region with no published S3 principal. */
|
|
27
|
+
export class ArchilRegionUnsupported extends Data.TaggedError("Archil.RegionUnsupported")<{
|
|
28
|
+
readonly region: string;
|
|
29
|
+
readonly message: string;
|
|
30
|
+
}> {}
|
|
31
|
+
|
|
32
|
+
export interface S3DiskProps extends Omit<DiskProps, "storage"> {
|
|
33
|
+
/** The bucket the disk keeps its files in. */
|
|
34
|
+
bucket: S3.Bucket | Effect.Effect<S3.Bucket, any, any>;
|
|
35
|
+
/** Prefix within the bucket, e.g. `"datasets/"`. */
|
|
36
|
+
prefix?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The bucket-policy statements that let Archil's role for `region` read and
|
|
41
|
+
* write `bucketArn`, only on behalf of disks in `organization`.
|
|
42
|
+
*/
|
|
43
|
+
export const archilBucketStatements = (input: {
|
|
44
|
+
readonly region: string;
|
|
45
|
+
readonly organization: string;
|
|
46
|
+
readonly bucketArn: string;
|
|
47
|
+
readonly prefix?: string;
|
|
48
|
+
}): PolicyStatement[] => {
|
|
49
|
+
const principal = ARCHIL_S3_PRINCIPALS[input.region]!;
|
|
50
|
+
const condition = {
|
|
51
|
+
StringEquals: { "aws:PrincipalTag/archilAccountId": input.organization },
|
|
52
|
+
};
|
|
53
|
+
const objects = `${input.bucketArn}/${input.prefix ?? ""}*`;
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
Sid: "ArchilList",
|
|
57
|
+
Effect: "Allow",
|
|
58
|
+
Principal: { AWS: principal },
|
|
59
|
+
Action: ["s3:ListBucket"],
|
|
60
|
+
Resource: input.bucketArn,
|
|
61
|
+
Condition: condition,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
Sid: "ArchilObjects",
|
|
65
|
+
Effect: "Allow",
|
|
66
|
+
Principal: { AWS: principal },
|
|
67
|
+
Action: ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:AbortMultipartUpload"],
|
|
68
|
+
Resource: objects,
|
|
69
|
+
Condition: condition,
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* An Archil disk that keeps its files in your own S3 bucket, with no access
|
|
76
|
+
* keys anywhere: the bucket's policy trusts Archil's IAM role for your
|
|
77
|
+
* region, only on behalf of disks in your Archil organization. The disk's
|
|
78
|
+
* files are ordinary objects in the bucket, so any S3 tool can read them.
|
|
79
|
+
*
|
|
80
|
+
* Set your Archil organization id in your profile (`alchemy profile edit
|
|
81
|
+
* --add Archil`) or as `ARCHIL_ORGANIZATION`; the policy can't be scoped
|
|
82
|
+
* without it.
|
|
83
|
+
* @see https://docs.archil.com/data-sources/s3-object-storage
|
|
84
|
+
*
|
|
85
|
+
* ### Creating a Disk on Your Bucket
|
|
86
|
+
* **Example:** A dataset disk on an S3 bucket
|
|
87
|
+
* ```typescript
|
|
88
|
+
* import * as ArchilAWS from "@alchemy.run/archil/AWS";
|
|
89
|
+
*
|
|
90
|
+
* export const Store = AWS.S3.Bucket("Store", {});
|
|
91
|
+
* export const Dataset = ArchilAWS.S3Disk("Dataset", { bucket: Store, prefix: "datasets/" });
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @primitive
|
|
95
|
+
* @product S3 Disk
|
|
96
|
+
*/
|
|
97
|
+
export const S3Disk = (id: string, props: S3DiskProps) =>
|
|
98
|
+
Effect.gen(function* () {
|
|
99
|
+
const { bucket: bucketRef, prefix, ...diskProps } = props;
|
|
100
|
+
const bucket = Effect.isEffect(bucketRef) ? yield* bucketRef : bucketRef;
|
|
101
|
+
if (!globalThis.__ALCHEMY_RUNTIME__) {
|
|
102
|
+
const { region, organization } = yield* account;
|
|
103
|
+
if (!ARCHIL_S3_PRINCIPALS[region]) {
|
|
104
|
+
return yield* new ArchilRegionUnsupported({
|
|
105
|
+
region,
|
|
106
|
+
message: `Archil publishes no S3 principal for ${region}.`,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (!organization) {
|
|
110
|
+
return yield* new ArchilOrganizationRequired({
|
|
111
|
+
message:
|
|
112
|
+
"ArchilAWS.S3Disk scopes the bucket policy to your Archil organization. Set it with `alchemy profile edit --add Archil` or ARCHIL_ORGANIZATION.",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
yield* bucket.bind(`Archil(${id})`, {
|
|
116
|
+
policyStatements: Output.map(bucket.bucketArn, (bucketArn) =>
|
|
117
|
+
archilBucketStatements({ region, organization, bucketArn, prefix }),
|
|
118
|
+
),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return yield* Disk(id, {
|
|
122
|
+
...diskProps,
|
|
123
|
+
storage: { type: "s3", bucketName: bucket.bucketName, bucketPrefix: prefix },
|
|
124
|
+
});
|
|
125
|
+
});
|
package/src/AWS/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./S3Disk.ts";
|