@axiom-lattice/microsandbox-service 0.0.3 → 0.0.5
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/CHANGELOG.md +12 -0
- package/dist/{chunk-VM3OJT5V.mjs → chunk-2X2Y4KVP.mjs} +46 -4
- package/dist/chunk-2X2Y4KVP.mjs.map +1 -0
- package/dist/{chunk-KRXZP6OH.mjs → chunk-A27D23SD.mjs} +40 -16
- package/dist/chunk-A27D23SD.mjs.map +1 -0
- package/dist/cli.js +82 -16
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +44 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/server.js +82 -16
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +2 -2
- package/package.json +1 -1
- package/src/__tests__/MicrosandboxRuntimeService.test.ts +31 -1
- package/src/__tests__/app.test.ts +142 -0
- package/src/__tests__/server-cli.test.ts +178 -29
- package/src/app.ts +29 -1
- package/src/lib/server-cli.ts +9 -3
- package/src/server.ts +35 -11
- package/src/services/MicrosandboxRuntimeService.ts +23 -1
- package/.turbo/turbo-build.log +0 -37
- package/dist/chunk-KRXZP6OH.mjs.map +0 -1
- package/dist/chunk-VM3OJT5V.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -57,7 +57,7 @@ var SandboxRegistry = class {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
// src/services/MicrosandboxRuntimeService.ts
|
|
60
|
-
import { Mount, Sandbox as Sandbox2 } from "microsandbox";
|
|
60
|
+
import { Mount, Sandbox as Sandbox2, Volume } from "microsandbox";
|
|
61
61
|
|
|
62
62
|
// src/services/SandboxMetadataStore.ts
|
|
63
63
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
@@ -160,6 +160,22 @@ var MicrosandboxRuntimeService = class {
|
|
|
160
160
|
}
|
|
161
161
|
return Mount.tmpfs({ sizeMib: definition.sizeMib });
|
|
162
162
|
}
|
|
163
|
+
async ensureNamedVolumes(volumes) {
|
|
164
|
+
if (!volumes) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const defaultQuotaMib = Number(process.env.MICROSANDBOX_VOLUME_QUOTA_MIB ?? "1024");
|
|
168
|
+
const namedVolumes = Object.values(volumes).filter((v) => v.type === "named");
|
|
169
|
+
await Promise.all(
|
|
170
|
+
namedVolumes.map(async (definition) => {
|
|
171
|
+
try {
|
|
172
|
+
await Volume.get(definition.name);
|
|
173
|
+
} catch {
|
|
174
|
+
await Volume.create({ name: definition.name, quotaMib: defaultQuotaMib });
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
);
|
|
178
|
+
}
|
|
163
179
|
getNative(name) {
|
|
164
180
|
return this.registry.get(name);
|
|
165
181
|
}
|
|
@@ -174,6 +190,7 @@ var MicrosandboxRuntimeService = class {
|
|
|
174
190
|
const image = input.image ?? process.env.MICROSANDBOX_IMAGE ?? "python:3.11-slim";
|
|
175
191
|
const cpus = input.cpus ?? Number(process.env.MICROSANDBOX_CPUS ?? "1");
|
|
176
192
|
const memoryMib = input.memoryMib ?? Number(process.env.MICROSANDBOX_MEMORY ?? "512");
|
|
193
|
+
await this.ensureNamedVolumes(input.volumes);
|
|
177
194
|
await this.registry.ensure(name, {
|
|
178
195
|
image,
|
|
179
196
|
cpus,
|
|
@@ -656,12 +673,37 @@ var ImageService = class {
|
|
|
656
673
|
// src/app.ts
|
|
657
674
|
function buildApp({
|
|
658
675
|
runtimeService = new MicrosandboxRuntimeService(),
|
|
659
|
-
imageService = new ImageService()
|
|
676
|
+
imageService = new ImageService(),
|
|
677
|
+
apiKey
|
|
660
678
|
} = {}) {
|
|
661
679
|
const app = fastify({ logger: false });
|
|
662
|
-
app.register(cors, {
|
|
680
|
+
app.register(cors, {
|
|
681
|
+
delegator: (request, callback) => {
|
|
682
|
+
callback(null, {
|
|
683
|
+
origin: true,
|
|
684
|
+
methods: request.headers["access-control-request-method"] ?? "*",
|
|
685
|
+
allowedHeaders: request.headers["access-control-request-headers"]
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
});
|
|
663
689
|
app.register(sensible);
|
|
664
690
|
app.register(multipart);
|
|
691
|
+
if (apiKey) {
|
|
692
|
+
app.addHook("onRequest", async (request, reply) => {
|
|
693
|
+
if (request.method === "OPTIONS" || !request.url.startsWith("/api/")) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (request.headers.authorization !== `Bearer ${apiKey}`) {
|
|
697
|
+
await reply.code(401).send({
|
|
698
|
+
success: false,
|
|
699
|
+
error: {
|
|
700
|
+
code: "UNAUTHORIZED",
|
|
701
|
+
message: "Unauthorized"
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
}
|
|
665
707
|
registerHealthRoutes(app);
|
|
666
708
|
registerSandboxRoutes(app, runtimeService);
|
|
667
709
|
registerImageRoutes(app, imageService);
|
|
@@ -677,4 +719,4 @@ export {
|
|
|
677
719
|
MicrosandboxRuntimeService,
|
|
678
720
|
buildApp
|
|
679
721
|
};
|
|
680
|
-
//# sourceMappingURL=chunk-
|
|
722
|
+
//# sourceMappingURL=chunk-2X2Y4KVP.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/SandboxRegistry.ts","../src/services/MicrosandboxRuntimeService.ts","../src/services/SandboxMetadataStore.ts","../src/app.ts","../src/lib/errors.ts","../src/lib/http.ts","../src/routes/health.ts","../src/controllers/images.ts","../src/schemas/images.ts","../src/routes/images.ts","../src/schemas/sandbox.ts","../src/controllers/sandbox.ts","../src/routes/sandbox.ts","../src/services/ImageService.ts"],"sourcesContent":["import { Sandbox } from \"microsandbox\";\n\nconst DEFAULT_IMAGE = process.env.MICROSANDBOX_IMAGE ?? \"daytonaio/sandbox:0.6.0\";\n\nexport class SandboxRegistry {\n private handles = new Map<string, unknown>();\n private creating = new Map<string, Promise<unknown>>();\n\n async ensure(name: string, createOptions?: Record<string, unknown>): Promise<unknown> {\n const cached = this.handles.get(name);\n if (cached) {\n return cached;\n }\n\n const inflight = this.creating.get(name);\n if (inflight) {\n return inflight;\n }\n\n const creation = (async () => {\n let native: unknown;\n const createConfig = {\n name,\n image: DEFAULT_IMAGE,\n ...(createOptions ?? {}),\n };\n\n try {\n const handle = await Sandbox.get(name);\n\n if (handle.status === \"running\") {\n native = await handle.connect();\n } else if (handle.status === \"stopped\") {\n native = await Sandbox.start(name);\n } else if (handle.status === \"crashed\" || handle.status === \"draining\") {\n await Sandbox.remove(name);\n }\n } catch {\n native = undefined;\n }\n\n if (!native) {\n native = await Sandbox.createDetached(createConfig);\n }\n\n this.handles.set(name, native);\n return native;\n })();\n\n this.creating.set(name, creation);\n void creation.finally(() => this.creating.delete(name));\n\n return creation;\n }\n\n get(name: string): unknown {\n const handle = this.handles.get(name);\n\n if (!handle) {\n throw new Error(`Sandbox ${name} not found`);\n }\n\n return handle;\n }\n\n delete(name: string): void {\n this.handles.delete(name);\n }\n}\n","import { Mount, Sandbox, Volume } from \"microsandbox\";\nimport type { EnsureSandboxInput, ListSandboxesQuery, ShellExecInput } from \"../schemas/sandbox\";\nimport { SandboxRegistry } from \"./SandboxRegistry\";\nimport {\n SandboxMetadataStore,\n type SandboxMetadataRecord,\n type SandboxMetadataStoreLike,\n} from \"./SandboxMetadataStore\";\n\ntype VolumeConfig = NonNullable<EnsureSandboxInput[\"volumes\"]>[string];\n\ntype RuntimeNative = {\n fs(): {\n readString(path: string): Promise<string>;\n write(path: string, data: Buffer): Promise<void>;\n list(\n path: string,\n options?: { recursive?: boolean }\n ): Promise<Array<{ path: string; kind: string; size?: number; modified?: unknown }>>;\n read(path: string): Promise<Buffer | Uint8Array | string>;\n };\n execWithConfig(config: {\n cmd: string;\n args?: string[];\n cwd?: string;\n timeoutMs?: number;\n }): Promise<{\n stdout(): string;\n stderr?(): string;\n code?: number;\n }>;\n stop(): Promise<void>;\n kill(): Promise<void>;\n};\n\nexport class MicrosandboxRuntimeService {\n constructor(private deps: { registry?: SandboxRegistry; metadataStore?: SandboxMetadataStoreLike } = {}) {}\n\n private get registry(): SandboxRegistry {\n return this.deps.registry ?? (this.deps.registry = new SandboxRegistry());\n }\n\n private get metadataStore(): SandboxMetadataStoreLike {\n return this.deps.metadataStore ?? (this.deps.metadataStore = new SandboxMetadataStore());\n }\n\n private toListItem(record: SandboxMetadataRecord): {\n name: string;\n status: string;\n image?: string;\n cpus?: number;\n memoryMib?: number;\n envCount: number;\n volumeCount: number;\n createdAt: string;\n updatedAt: string;\n } {\n return {\n name: record.name,\n status: record.status,\n image: record.image,\n cpus: record.cpus,\n memoryMib: record.memoryMib,\n envCount: Object.keys(record.env).length,\n volumeCount: Object.keys(record.volumes).length,\n createdAt: record.createdAt,\n updatedAt: record.updatedAt,\n };\n }\n\n private buildVolumes(volumes?: EnsureSandboxInput[\"volumes\"]): Record<string, unknown> | undefined {\n if (!volumes) {\n return undefined;\n }\n\n return Object.fromEntries(\n Object.entries(volumes).map(([mountPath, definition]) => [mountPath, this.buildVolume(definition)])\n );\n }\n\n private buildVolume(definition: VolumeConfig): unknown {\n if (definition.type === \"bind\") {\n return Mount.bind(definition.source, { readonly: definition.readonly ?? false });\n }\n\n if (definition.type === \"named\") {\n return Mount.named(definition.name, { readonly: definition.readonly ?? false });\n }\n\n return Mount.tmpfs({ sizeMib: definition.sizeMib });\n }\n\n private async ensureNamedVolumes(volumes?: EnsureSandboxInput[\"volumes\"]): Promise<void> {\n if (!volumes) {\n return;\n }\n\n const defaultQuotaMib = Number(process.env.MICROSANDBOX_VOLUME_QUOTA_MIB ?? \"1024\");\n\n const namedVolumes = Object.values(volumes).filter((v): v is Extract<VolumeConfig, { type: \"named\" }> => v.type === \"named\");\n\n await Promise.all(\n namedVolumes.map(async (definition) => {\n try {\n await Volume.get(definition.name);\n } catch {\n await Volume.create({ name: definition.name, quotaMib: defaultQuotaMib });\n }\n })\n );\n }\n\n private getNative(name: string): RuntimeNative {\n return this.registry.get(name) as RuntimeNative;\n }\n\n private async getOrEnsureNative(name: string): Promise<RuntimeNative> {\n try {\n return this.getNative(name);\n } catch {\n return (await this.registry.ensure(name)) as RuntimeNative;\n }\n }\n\n async ensureSandbox(name: string, input: EnsureSandboxInput): Promise<{\n name: string;\n status: string;\n }> {\n const image = input.image ?? process.env.MICROSANDBOX_IMAGE ?? \"python:3.11-slim\";\n const cpus = input.cpus ?? Number(process.env.MICROSANDBOX_CPUS ?? \"1\");\n const memoryMib = input.memoryMib ?? Number(process.env.MICROSANDBOX_MEMORY ?? \"512\");\n\n await this.ensureNamedVolumes(input.volumes);\n\n await this.registry.ensure(name, {\n image,\n cpus,\n memoryMib,\n env: input.env,\n volumes: this.buildVolumes(input.volumes),\n });\n\n const existing = await this.metadataStore.get(name);\n const now = new Date().toISOString();\n\n await this.metadataStore.upsert({\n name,\n status: \"running\",\n image,\n cpus,\n memoryMib,\n env: input.env ?? {},\n volumes: input.volumes ?? {},\n createdAt: existing?.createdAt ?? now,\n updatedAt: now,\n });\n\n return {\n name,\n status: \"running\",\n };\n }\n\n async startSandbox(name: string): Promise<{ name: string; status: string }> {\n await Sandbox.start(name);\n this.registry.delete(name);\n await this.registry.ensure(name);\n await this.metadataStore.patchStatus(name, \"running\");\n return { name, status: \"running\" };\n }\n\n async stopSandbox(name: string): Promise<{ name: string; status: string }> {\n const native = await this.getOrEnsureNative(name);\n await native.stop();\n this.registry.delete(name);\n await this.metadataStore.patchStatus(name, \"stopped\");\n return { name, status: \"stopped\" };\n }\n\n async killSandbox(name: string): Promise<{ name: string; status: string }> {\n const native = await this.getOrEnsureNative(name);\n await native.kill();\n this.registry.delete(name);\n await this.metadataStore.patchStatus(name, \"unknown\");\n return { name, status: \"unknown\" };\n }\n\n async deleteSandbox(name: string): Promise<{ name: string; status: string }> {\n try {\n await this.getNative(name).kill();\n } catch {\n // Ignore missing cached native handle before remove.\n }\n\n await Sandbox.remove(name);\n this.registry.delete(name);\n await this.metadataStore.remove(name);\n\n return { name, status: \"unknown\" };\n }\n\n async listSandboxes(_query: ListSandboxesQuery): Promise<{\n items: Array<{\n name: string;\n status: string;\n image?: string;\n cpus?: number;\n memoryMib?: number;\n envCount: number;\n volumeCount: number;\n createdAt: string;\n updatedAt: string;\n }>;\n total: number;\n }> {\n const items = (await this.metadataStore.list())\n .filter((record) => {\n if (_query.status && record.status !== _query.status) {\n return false;\n }\n\n if (_query.image && record.image !== _query.image) {\n return false;\n }\n\n if (_query.search) {\n const haystack = [record.name, record.image, ...Object.keys(record.env)]\n .filter((value): value is string => Boolean(value))\n .join(\" \")\n .toLowerCase();\n\n if (!haystack.includes(_query.search.toLowerCase())) {\n return false;\n }\n }\n\n return true;\n })\n .map((record) => this.toListItem(record));\n\n return { items, total: items.length };\n }\n\n async getSandbox(name: string): Promise<SandboxMetadataRecord | undefined> {\n return await this.metadataStore.get(name);\n }\n\n async getStatus(name: string): Promise<{ name: string; status: string }> {\n try {\n this.getNative(name);\n return { name, status: \"running\" };\n } catch {\n return { name, status: \"unknown\" };\n }\n }\n\n async readFile(sandboxName: string, path: string): Promise<{ path: string; content: string }> {\n const content = await this.getNative(sandboxName).fs().readString(path);\n return { path, content };\n }\n\n async writeFile(sandboxName: string, path: string, content: string): Promise<{ path: string }> {\n await this.getNative(sandboxName).fs().write(path, Buffer.from(content));\n return { path };\n }\n\n async listPath(\n sandboxName: string,\n path: string,\n recursive?: boolean\n ): Promise<{ entries: Array<{ path: string; type: string }> }> {\n const entries = await this.getNative(sandboxName).fs().list(path, { recursive });\n return {\n entries: entries.map((entry) => ({\n path: entry.path,\n type: entry.kind,\n })),\n };\n }\n\n async findFiles(sandboxName: string, path: string, pattern: string): Promise<{ files: string[] }> {\n const output = await this.getNative(sandboxName).execWithConfig({\n cmd: \"find\",\n args: [path, \"-name\", pattern, \"-type\", \"f\"],\n });\n return { files: output.stdout().split(\"\\n\").filter(Boolean) };\n }\n\n async searchInFile(\n sandboxName: string,\n path: string,\n query: string\n ): Promise<{ matches: Array<{ line: number; content: string }> }> {\n let stdout: string;\n\n try {\n const output = await this.getNative(sandboxName).execWithConfig({\n cmd: \"grep\",\n args: [\"-n\", \"-E\", query, path],\n });\n stdout = output.stdout();\n } catch (error) {\n if (typeof error === \"object\" && error !== null && \"code\" in error && error.code === 1) {\n return { matches: [] };\n }\n\n throw error;\n }\n\n return {\n matches: stdout\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => {\n const separator = line.indexOf(\":\");\n return {\n line: Number(line.slice(0, separator)),\n content: line.slice(separator + 1),\n };\n }),\n };\n }\n\n async replaceInFile(\n sandboxName: string,\n input: { path: string; search: string; replace: string }\n ): Promise<{ replaced: number }> {\n const fs = this.getNative(sandboxName).fs();\n const original = await fs.readString(input.path);\n\n if (!input.search) {\n return { replaced: 0 };\n }\n\n const occurrences = original.split(input.search).length - 1;\n if (occurrences === 0) {\n return { replaced: 0 };\n }\n\n const updated = original.split(input.search).join(input.replace);\n await fs.write(input.path, Buffer.from(updated));\n\n return { replaced: occurrences };\n }\n\n async uploadFile(sandboxName: string, path: string, contentBase64: string): Promise<{ path: string }> {\n await this.getNative(sandboxName).fs().write(path, Buffer.from(contentBase64, \"base64\"));\n return { path };\n }\n\n async downloadFile(sandboxName: string, path: string): Promise<{ path: string; contentBase64: string }> {\n const data = await this.getNative(sandboxName).fs().read(path);\n const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);\n return { path, contentBase64: buffer.toString(\"base64\") };\n }\n\n async execCommand(input: ShellExecInput): Promise<{ stdout: string; stderr: string; exitCode: number }> {\n const output = await this.getNative(input.sandboxName).execWithConfig({\n cmd: \"sh\",\n args: [\"-c\", input.command],\n cwd: input.exec_dir,\n timeoutMs: input.timeout ? input.timeout * 1000 : undefined,\n });\n\n return {\n stdout: output.stdout(),\n stderr: output.stderr ? output.stderr() : \"\",\n exitCode: output.code ?? 0,\n };\n }\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { EnsureSandboxInput } from \"../schemas/sandbox\";\n\nexport type SandboxStatus = \"running\" | \"stopped\" | \"unknown\";\n\nexport type SandboxMetadataRecord = {\n name: string;\n status: SandboxStatus;\n image?: string;\n cpus?: number;\n memoryMib?: number;\n env: Record<string, string>;\n volumes: NonNullable<EnsureSandboxInput[\"volumes\"]>;\n createdAt: string;\n updatedAt: string;\n};\n\ntype SandboxMetadataFile = {\n sandboxes: SandboxMetadataRecord[];\n};\n\nfunction isSandboxMetadataFile(value: unknown): value is SandboxMetadataFile {\n return typeof value === \"object\" && value !== null && \"sandboxes\" in value && Array.isArray(value.sandboxes);\n}\n\nexport interface SandboxMetadataStoreLike {\n list(): Promise<SandboxMetadataRecord[]>;\n get(name: string): Promise<SandboxMetadataRecord | undefined>;\n upsert(record: SandboxMetadataRecord): Promise<SandboxMetadataRecord>;\n patchStatus(name: string, status: SandboxStatus): Promise<void>;\n remove(name: string): Promise<void>;\n}\n\nexport class SandboxMetadataStore implements SandboxMetadataStoreLike {\n constructor(private readonly options: { filePath?: string } = {}) {}\n\n private get filePath(): string {\n return this.options.filePath ?? process.env.MICROSANDBOX_METADATA_FILE ?? \".microsandbox/metadata.json\";\n }\n\n private async readAll(): Promise<SandboxMetadataFile> {\n try {\n const content = await readFile(this.filePath, \"utf8\");\n const parsed: unknown = JSON.parse(content);\n\n if (!isSandboxMetadataFile(parsed)) {\n throw new Error(\"Invalid sandbox metadata file\");\n }\n\n return parsed;\n } catch (error) {\n if (typeof error === \"object\" && error !== null && \"code\" in error && error.code === \"ENOENT\") {\n return { sandboxes: [] };\n }\n\n throw error;\n }\n }\n\n private async writeAll(file: SandboxMetadataFile): Promise<void> {\n await mkdir(dirname(this.filePath), { recursive: true });\n await writeFile(this.filePath, JSON.stringify(file, null, 2));\n }\n\n async list(): Promise<SandboxMetadataRecord[]> {\n const file = await this.readAll();\n return file.sandboxes.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));\n }\n\n async get(name: string): Promise<SandboxMetadataRecord | undefined> {\n const file = await this.readAll();\n return file.sandboxes.find((entry) => entry.name === name);\n }\n\n async upsert(record: SandboxMetadataRecord): Promise<SandboxMetadataRecord> {\n const file = await this.readAll();\n const next = file.sandboxes.filter((entry) => entry.name !== record.name);\n next.push(record);\n await this.writeAll({ sandboxes: next });\n return record;\n }\n\n async patchStatus(name: string, status: SandboxStatus): Promise<void> {\n const current = await this.get(name);\n if (!current) {\n return;\n }\n\n await this.upsert({ ...current, status, updatedAt: new Date().toISOString() });\n }\n\n async remove(name: string): Promise<void> {\n const file = await this.readAll();\n await this.writeAll({ sandboxes: file.sandboxes.filter((entry) => entry.name !== name) });\n }\n}\n","import cors from \"@fastify/cors\";\nimport multipart from \"@fastify/multipart\";\nimport sensible from \"@fastify/sensible\";\nimport fastify, { type FastifyInstance } from \"fastify\";\nimport { toErrorResponse } from \"./lib/errors\";\nimport { registerHealthRoutes } from \"./routes/health\";\nimport { registerImageRoutes } from \"./routes/images\";\nimport { registerSandboxRoutes } from \"./routes/sandbox\";\nimport { ImageService } from \"./services/ImageService\";\nimport { MicrosandboxRuntimeService } from \"./services/MicrosandboxRuntimeService\";\nimport type { RuntimeService } from \"./types/runtime-service\";\n\nexport function buildApp({\n runtimeService = new MicrosandboxRuntimeService(),\n imageService = new ImageService(),\n apiKey,\n}: {\n runtimeService?: RuntimeService;\n imageService?: ImageService;\n apiKey?: string;\n} = {}): FastifyInstance {\n const app = fastify({ logger: false });\n\n app.register(cors, {\n delegator: (request, callback) => {\n callback(null, {\n origin: true,\n methods: request.headers[\"access-control-request-method\"] ?? \"*\",\n allowedHeaders: request.headers[\"access-control-request-headers\"],\n });\n },\n });\n app.register(sensible);\n app.register(multipart);\n\n if (apiKey) {\n app.addHook(\"onRequest\", async (request, reply) => {\n if (request.method === \"OPTIONS\" || !request.url.startsWith(\"/api/\")) {\n return;\n }\n\n if (request.headers.authorization !== `Bearer ${apiKey}`) {\n await reply.code(401).send({\n success: false,\n error: {\n code: \"UNAUTHORIZED\",\n message: \"Unauthorized\",\n },\n });\n }\n });\n }\n\n registerHealthRoutes(app);\n registerSandboxRoutes(app, runtimeService);\n registerImageRoutes(app, imageService);\n\n app.setErrorHandler((error, _request, reply) => {\n const { statusCode, body } = toErrorResponse(error);\n reply.status(statusCode).send(body);\n });\n\n return app;\n}\n","export class HttpError extends Error {\n constructor(\n public statusCode: number,\n public code: string,\n message: string\n ) {\n super(message);\n }\n}\n\nexport function toErrorResponse(error: unknown): {\n statusCode: number;\n body: {\n success: false;\n error: {\n code: string;\n message: string;\n };\n };\n} {\n if (error instanceof HttpError) {\n return {\n statusCode: error.statusCode,\n body: {\n success: false,\n error: {\n code: error.code,\n message: error.message,\n },\n },\n };\n }\n\n return {\n statusCode: 500,\n body: {\n success: false,\n error: {\n code: \"INTERNAL_ERROR\",\n message: error instanceof Error ? error.message : String(error),\n },\n },\n };\n}\n","export type SuccessResponse<T> = {\n success: true;\n data: T;\n};\n\nexport function ok<T>(data: T): SuccessResponse<T> {\n return {\n success: true,\n data,\n };\n}\n","import type { FastifyInstance } from \"fastify\";\nimport { ok } from \"../lib/http\";\n\nexport function registerHealthRoutes(app: FastifyInstance): void {\n app.get(\"/health\", async () => ok({ status: \"ok\" }));\n}\n","import type { FastifyReply, FastifyRequest } from \"fastify\";\nimport { ZodError } from \"zod\";\nimport { HttpError } from \"../lib/errors\";\nimport { ok } from \"../lib/http\";\nimport { ImageService } from \"../services/ImageService\";\nimport { imageRefQuerySchema, pullImageSchema } from \"../schemas/images\";\n\nfunction parseOrThrow<T>(parse: () => T): T {\n try {\n return parse();\n } catch (error) {\n if (error instanceof ZodError) {\n throw new HttpError(400, \"INVALID_REQUEST\", error.issues[0]?.message ?? \"Invalid request\");\n }\n\n throw error;\n }\n}\n\nexport function createImageController(imageService: ImageService) {\n return {\n listImages: async (_request: FastifyRequest, reply: FastifyReply) => {\n return reply.send(ok(await imageService.listImages()));\n },\n pullImage: async (request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply) => {\n const body = parseOrThrow(() => pullImageSchema.parse(request.body ?? {}));\n return reply.send(ok(await imageService.pullImage(body)));\n },\n getImage: async (request: FastifyRequest<{ Querystring: unknown }>, reply: FastifyReply) => {\n const query = parseOrThrow(() => imageRefQuerySchema.parse(request.query ?? {}));\n return reply.send(ok(await imageService.getImage(query.ref)));\n },\n deleteImage: async (request: FastifyRequest<{ Querystring: unknown }>, reply: FastifyReply) => {\n const query = parseOrThrow(() => imageRefQuerySchema.parse(request.query ?? {}));\n return reply.send(ok(await imageService.deleteImage(query.ref)));\n },\n };\n}\n","import z from \"zod\";\n\nexport const imageRefQuerySchema = z.object({\n ref: z.string().min(1),\n});\n\nexport const pullImageSchema = z.object({\n ref: z.string().min(1),\n});\n\nexport type ImageRefQuery = z.infer<typeof imageRefQuerySchema>;\nexport type PullImageInput = z.infer<typeof pullImageSchema>;\n","import type { FastifyInstance } from \"fastify\";\nimport { createImageController } from \"../controllers/images\";\nimport { ImageService } from \"../services/ImageService\";\n\nexport function registerImageRoutes(app: FastifyInstance, imageService: ImageService): void {\n const controller = createImageController(imageService);\n\n app.get(\"/api/images\", controller.listImages);\n app.post(\"/api/images/pull\", controller.pullImage);\n app.get(\"/api/images/detail\", controller.getImage);\n app.delete(\"/api/images/detail\", controller.deleteImage);\n}\n","import z from \"zod\";\n\nconst bindMountSchema = z.object({\n type: z.literal(\"bind\"),\n source: z.string().min(1),\n readonly: z.boolean().optional(),\n});\n\nconst namedMountSchema = z.object({\n type: z.literal(\"named\"),\n name: z.string().min(1),\n readonly: z.boolean().optional(),\n});\n\nconst tmpfsMountSchema = z.object({\n type: z.literal(\"tmpfs\"),\n sizeMib: z.number().int().positive().optional(),\n});\n\nexport const volumeSchema = z.union([\n bindMountSchema,\n namedMountSchema,\n tmpfsMountSchema,\n]);\n\nexport const ensureSandboxSchema = z.object({\n image: z.string().optional(),\n cpus: z.number().int().positive().optional(),\n memoryMib: z.number().int().positive().optional(),\n env: z.record(z.string()).optional(),\n volumes: z.record(volumeSchema).optional(),\n});\n\nexport const sandboxNameParamsSchema = z.object({\n name: z.string().min(1),\n});\n\nexport const listSandboxesQuerySchema = z.object({\n status: z.enum([\"running\", \"stopped\", \"unknown\"]).optional(),\n image: z.string().min(1).optional(),\n search: z.string().min(1).optional(),\n});\n\nconst sandboxAndPathSchema = z.object({\n sandboxName: z.string().min(1),\n path: z.string().min(1),\n});\n\nexport const readFileSchema = sandboxAndPathSchema;\n\nexport const writeFileSchema = sandboxAndPathSchema.extend({\n content: z.string(),\n});\n\nexport const listPathSchema = sandboxAndPathSchema.extend({\n recursive: z.boolean().optional(),\n});\n\nexport const findFilesSchema = sandboxAndPathSchema.extend({\n pattern: z.string().min(1),\n});\n\nexport const searchInFileSchema = sandboxAndPathSchema.extend({\n query: z.string().min(1),\n});\n\nexport const replaceInFileSchema = sandboxAndPathSchema.extend({\n search: z.string(),\n replace: z.string(),\n});\n\nexport const uploadFileSchema = sandboxAndPathSchema.and(\n z.union([\n z.object({ contentBase64: z.string() }),\n z.object({ content: z.string().transform((content) => Buffer.from(content).toString(\"base64\")) }),\n ])\n);\n\nexport const downloadFileSchema = sandboxAndPathSchema;\n\nexport const shellExecSchema = z.object({\n sandboxName: z.string().min(1),\n command: z.string().min(1),\n exec_dir: z.string().optional(),\n timeout: z.number().int().positive().optional(),\n});\n\nexport type EnsureSandboxInput = z.infer<typeof ensureSandboxSchema>;\nexport type ListSandboxesQuery = z.infer<typeof listSandboxesQuerySchema>;\nexport type ShellExecInput = z.infer<typeof shellExecSchema>;\n","import type { FastifyReply, FastifyRequest } from \"fastify\";\nimport {\n downloadFileSchema,\n ensureSandboxSchema,\n findFilesSchema,\n listSandboxesQuerySchema,\n listPathSchema,\n readFileSchema,\n replaceInFileSchema,\n sandboxNameParamsSchema,\n searchInFileSchema,\n shellExecSchema,\n uploadFileSchema,\n writeFileSchema,\n} from \"../schemas/sandbox\";\nimport { HttpError } from \"../lib/errors\";\nimport { ok } from \"../lib/http\";\nimport type { RuntimeService } from \"../types/runtime-service\";\n\nexport function createSandboxController(runtimeService: RuntimeService): {\n ensureSandbox(\n request: FastifyRequest<{ Params: { name: string }; Body: unknown }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n startSandbox(\n request: FastifyRequest<{ Params: { name: string } }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n stopSandbox(\n request: FastifyRequest<{ Params: { name: string } }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n killSandbox(\n request: FastifyRequest<{ Params: { name: string } }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n deleteSandbox(\n request: FastifyRequest<{ Params: { name: string } }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n listSandboxes(\n request: FastifyRequest<{ Querystring: unknown }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n getSandbox(\n request: FastifyRequest<{ Params: { name: string } }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n getStatus(\n request: FastifyRequest<{ Params: { name: string } }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n readFile(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n writeFile(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n listPath(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n findFiles(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n searchInFile(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n replaceInFile(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n uploadFile(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n downloadFile(\n request: FastifyRequest<{ Querystring: unknown }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n execCommand(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n} {\n return {\n ensureSandbox: async (\n request: FastifyRequest<{ Params: { name: string }; Body: unknown }>,\n reply: FastifyReply\n ) => {\n const body = ensureSandboxSchema.parse(request.body ?? {});\n const result = await runtimeService.ensureSandbox(request.params.name, body);\n return reply.send(ok(result));\n },\n\n startSandbox: async (request, reply) => {\n return reply.send(ok(await runtimeService.startSandbox(request.params.name)));\n },\n\n stopSandbox: async (request, reply) => {\n return reply.send(ok(await runtimeService.stopSandbox(request.params.name)));\n },\n\n killSandbox: async (request, reply) => {\n return reply.send(ok(await runtimeService.killSandbox(request.params.name)));\n },\n\n deleteSandbox: async (request, reply) => {\n return reply.send(ok(await runtimeService.deleteSandbox(request.params.name)));\n },\n\n listSandboxes: async (request, reply) => {\n const query = listSandboxesQuerySchema.parse(request.query ?? {});\n return reply.send(ok(await runtimeService.listSandboxes(query)));\n },\n\n getSandbox: async (request, reply) => {\n const params = sandboxNameParamsSchema.parse(request.params ?? {});\n const sandbox = await runtimeService.getSandbox(params.name);\n\n if (!sandbox) {\n throw new HttpError(404, \"SANDBOX_NOT_FOUND\", `Sandbox '${params.name}' not found`);\n }\n\n return reply.send(ok(sandbox));\n },\n\n getStatus: async (request, reply) => {\n return reply.send(ok(await runtimeService.getStatus(request.params.name)));\n },\n\n readFile: async (request, reply) => {\n const body = readFileSchema.parse(request.body ?? {});\n return reply.send(ok(await runtimeService.readFile(body.sandboxName, body.path)));\n },\n\n writeFile: async (request, reply) => {\n const body = writeFileSchema.parse(request.body ?? {});\n return reply.send(ok(await runtimeService.writeFile(body.sandboxName, body.path, body.content)));\n },\n\n listPath: async (request, reply) => {\n const body = listPathSchema.parse(request.body ?? {});\n return reply.send(ok(await runtimeService.listPath(body.sandboxName, body.path, body.recursive)));\n },\n\n findFiles: async (request, reply) => {\n const body = findFilesSchema.parse(request.body ?? {});\n return reply.send(ok(await runtimeService.findFiles(body.sandboxName, body.path, body.pattern)));\n },\n\n searchInFile: async (request, reply) => {\n const body = searchInFileSchema.parse(request.body ?? {});\n return reply.send(ok(await runtimeService.searchInFile(body.sandboxName, body.path, body.query)));\n },\n\n replaceInFile: async (request, reply) => {\n const body = replaceInFileSchema.parse(request.body ?? {});\n return reply.send(\n ok(\n await runtimeService.replaceInFile(body.sandboxName, {\n path: body.path,\n search: body.search,\n replace: body.replace,\n })\n )\n );\n },\n\n uploadFile: async (request, reply) => {\n const body = uploadFileSchema.parse(request.body ?? {});\n return reply.send(ok(await runtimeService.uploadFile(body.sandboxName, body.path, body.contentBase64)));\n },\n\n downloadFile: async (request, reply) => {\n const query = downloadFileSchema.parse(request.query ?? {});\n return reply.send(ok(await runtimeService.downloadFile(query.sandboxName, query.path)));\n },\n\n execCommand: async (request, reply) => {\n const body = shellExecSchema.parse(request.body ?? {});\n return reply.send(ok(await runtimeService.execCommand(body)));\n },\n };\n}\n","import type { FastifyInstance } from \"fastify\";\nimport { createSandboxController } from \"../controllers/sandbox\";\nimport type { RuntimeService } from \"../types/runtime-service\";\n\nexport function registerSandboxRoutes(\n app: FastifyInstance,\n runtimeService: RuntimeService\n): void {\n const controller = createSandboxController(runtimeService);\n\n app.get(\"/api/sandboxes\", controller.listSandboxes);\n app.get(\"/api/sandboxes/:name\", controller.getSandbox);\n app.put(\"/api/sandboxes/:name\", controller.ensureSandbox);\n app.post(\"/api/sandboxes/:name/start\", controller.startSandbox);\n app.post(\"/api/sandboxes/:name/stop\", controller.stopSandbox);\n app.post(\"/api/sandboxes/:name/kill\", controller.killSandbox);\n app.delete(\"/api/sandboxes/:name\", controller.deleteSandbox);\n app.get(\"/api/sandboxes/:name/status\", controller.getStatus);\n\n app.post(\"/api/files/read\", controller.readFile);\n app.post(\"/api/files/write\", controller.writeFile);\n app.post(\"/api/files/list\", controller.listPath);\n app.post(\"/api/files/find\", controller.findFiles);\n app.post(\"/api/files/search\", controller.searchInFile);\n app.post(\"/api/files/replace\", controller.replaceInFile);\n app.post(\"/api/files/upload\", controller.uploadFile);\n app.get(\"/api/files/download\", controller.downloadFile);\n\n app.post(\"/api/shell/exec\", controller.execCommand);\n}\n","import type { PullImageInput } from \"../schemas/images\";\n\nexport type ImageRecord = {\n ref: string;\n sourceType: string;\n cached: boolean;\n size?: number;\n createdAt?: string;\n lastUsedAt?: string;\n};\n\ntype ImageBindings = {\n list(): Promise<ImageRecord[]>;\n pull(ref: string): Promise<ImageRecord>;\n inspect(ref: string): Promise<ImageRecord>;\n remove(ref: string): Promise<void>;\n};\n\nexport class ImageService {\n constructor(\n private readonly deps: {\n bindings?: ImageBindings;\n } = {}\n ) {}\n\n private get bindings(): ImageBindings {\n return (\n this.deps.bindings ?? {\n list: async () => [],\n pull: async (ref: string) => ({ ref, sourceType: \"oci\", cached: true }),\n inspect: async (ref: string) => ({ ref, sourceType: \"oci\", cached: true }),\n remove: async () => undefined,\n }\n );\n }\n\n async listImages(): Promise<{ items: ImageRecord[]; total: number }> {\n const items = await this.bindings.list();\n return { items, total: items.length };\n }\n\n async pullImage(input: PullImageInput): Promise<ImageRecord> {\n return await this.bindings.pull(input.ref);\n }\n\n async getImage(ref: string): Promise<ImageRecord> {\n return await this.bindings.inspect(ref);\n }\n\n async deleteImage(ref: string): Promise<{ ref: string }> {\n await this.bindings.remove(ref);\n return { ref };\n }\n}\n"],"mappings":"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","names":["Sandbox","Sandbox","z"]}
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildApp
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-2X2Y4KVP.mjs";
|
|
4
4
|
|
|
5
5
|
// src/lib/server-cli.ts
|
|
6
6
|
function parsePort(raw, source) {
|
|
7
7
|
const value = Number(raw);
|
|
8
|
-
if (!Number.isInteger(value) || value <= 0) {
|
|
8
|
+
if (!Number.isInteger(value) || value <= 0 || value > 65535) {
|
|
9
9
|
throw new Error(`Invalid value for ${source}: ${raw}`);
|
|
10
10
|
}
|
|
11
11
|
return value;
|
|
12
12
|
}
|
|
13
|
+
function isMissingOptionValue(raw) {
|
|
14
|
+
return !raw || raw.startsWith("--");
|
|
15
|
+
}
|
|
13
16
|
function parseServerArgs(argv) {
|
|
14
17
|
const parsed = {};
|
|
15
18
|
for (let index = 0; index < argv.length; index += 1) {
|
|
16
19
|
const token = argv[index];
|
|
17
20
|
if (token === "--port") {
|
|
18
21
|
const raw = argv[index + 1];
|
|
19
|
-
if (
|
|
22
|
+
if (isMissingOptionValue(raw)) {
|
|
20
23
|
throw new Error("Missing value for --port");
|
|
21
24
|
}
|
|
22
25
|
parsed.port = parsePort(raw, "--port");
|
|
@@ -25,7 +28,7 @@ function parseServerArgs(argv) {
|
|
|
25
28
|
}
|
|
26
29
|
if (token === "--host") {
|
|
27
30
|
const raw = argv[index + 1];
|
|
28
|
-
if (
|
|
31
|
+
if (isMissingOptionValue(raw)) {
|
|
29
32
|
throw new Error("Missing value for --host");
|
|
30
33
|
}
|
|
31
34
|
parsed.host = raw;
|
|
@@ -40,7 +43,8 @@ function resolveServerConfig({
|
|
|
40
43
|
}) {
|
|
41
44
|
return {
|
|
42
45
|
host: args.host ?? env.HOST ?? "0.0.0.0",
|
|
43
|
-
port: args.port ?? (env.PORT ? parsePort(env.PORT, "PORT") : 4002)
|
|
46
|
+
port: args.port ?? (env.PORT ? parsePort(env.PORT, "PORT") : 4002),
|
|
47
|
+
apiKey: env.MICROSANDBOX_API_KEY
|
|
44
48
|
};
|
|
45
49
|
}
|
|
46
50
|
|
|
@@ -48,29 +52,49 @@ function resolveServerConfig({
|
|
|
48
52
|
async function startServer({
|
|
49
53
|
argv = process.argv.slice(2),
|
|
50
54
|
env = process.env,
|
|
51
|
-
app
|
|
55
|
+
app
|
|
52
56
|
} = {}) {
|
|
53
57
|
const config = resolveServerConfig({ args: parseServerArgs(argv), env });
|
|
58
|
+
const resolvedApp = app ?? buildApp({ apiKey: config.apiKey });
|
|
59
|
+
const onSigInt = () => {
|
|
60
|
+
void shutdown(0);
|
|
61
|
+
};
|
|
62
|
+
const onSigTerm = () => {
|
|
63
|
+
void shutdown(0);
|
|
64
|
+
};
|
|
65
|
+
const removeSignalHandlers = () => {
|
|
66
|
+
process.off("SIGINT", onSigInt);
|
|
67
|
+
process.off("SIGTERM", onSigTerm);
|
|
68
|
+
};
|
|
69
|
+
const close = async () => {
|
|
70
|
+
removeSignalHandlers();
|
|
71
|
+
return resolvedApp.close();
|
|
72
|
+
};
|
|
73
|
+
const startedApp = {
|
|
74
|
+
listen: resolvedApp.listen.bind(resolvedApp),
|
|
75
|
+
close
|
|
76
|
+
};
|
|
54
77
|
const shutdown = async (code) => {
|
|
55
78
|
try {
|
|
56
|
-
await
|
|
79
|
+
await close();
|
|
57
80
|
process.exit(code);
|
|
58
81
|
} catch (error) {
|
|
59
82
|
console.error("Failed to shutdown microsandbox-service cleanly", error);
|
|
60
83
|
process.exit(1);
|
|
61
84
|
}
|
|
62
85
|
};
|
|
63
|
-
process.once("SIGINT",
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
|
|
86
|
+
process.once("SIGINT", onSigInt);
|
|
87
|
+
process.once("SIGTERM", onSigTerm);
|
|
88
|
+
try {
|
|
89
|
+
await resolvedApp.listen({ port: config.port, host: config.host });
|
|
90
|
+
return startedApp;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
removeSignalHandlers();
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
71
95
|
}
|
|
72
96
|
|
|
73
97
|
export {
|
|
74
98
|
startServer
|
|
75
99
|
};
|
|
76
|
-
//# sourceMappingURL=chunk-
|
|
100
|
+
//# sourceMappingURL=chunk-A27D23SD.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/server-cli.ts","../src/server.ts"],"sourcesContent":["export interface ParsedServerArgs {\n host?: string;\n port?: number;\n}\n\nexport interface ResolvedServerConfig {\n host: string;\n port: number;\n apiKey?: string;\n}\n\nfunction parsePort(raw: string, source: string): number {\n const value = Number(raw);\n if (!Number.isInteger(value) || value <= 0 || value > 65535) {\n throw new Error(`Invalid value for ${source}: ${raw}`);\n }\n return value;\n}\n\nfunction isMissingOptionValue(raw: string | undefined): raw is undefined {\n return !raw || raw.startsWith(\"--\");\n}\n\nexport function parseServerArgs(argv: string[]): ParsedServerArgs {\n const parsed: ParsedServerArgs = {};\n\n for (let index = 0; index < argv.length; index += 1) {\n const token = argv[index];\n\n if (token === \"--port\") {\n const raw = argv[index + 1];\n if (isMissingOptionValue(raw)) {\n throw new Error(\"Missing value for --port\");\n }\n parsed.port = parsePort(raw, \"--port\");\n index += 1;\n continue;\n }\n\n if (token === \"--host\") {\n const raw = argv[index + 1];\n if (isMissingOptionValue(raw)) {\n throw new Error(\"Missing value for --host\");\n }\n parsed.host = raw;\n index += 1;\n }\n }\n\n return parsed;\n}\n\nexport function resolveServerConfig({\n args,\n env,\n}: {\n args: ParsedServerArgs;\n env: NodeJS.ProcessEnv;\n}): ResolvedServerConfig {\n return {\n host: args.host ?? env.HOST ?? \"0.0.0.0\",\n port: args.port ?? (env.PORT ? parsePort(env.PORT, \"PORT\") : 4002),\n apiKey: env.MICROSANDBOX_API_KEY,\n };\n}\n","import type { FastifyInstance } from \"fastify\";\nimport { buildApp } from \"./app\";\nimport { parseServerArgs, resolveServerConfig } from \"./lib/server-cli\";\n\nexport async function startServer({\n argv = process.argv.slice(2),\n env = process.env,\n app,\n}: {\n argv?: string[];\n env?: NodeJS.ProcessEnv;\n app?: Pick<FastifyInstance, \"listen\" | \"close\">;\n} = {}): Promise<Pick<FastifyInstance, \"listen\" | \"close\">> {\n const config = resolveServerConfig({ args: parseServerArgs(argv), env });\n const resolvedApp = app ?? buildApp({ apiKey: config.apiKey });\n\n const onSigInt = () => {\n void shutdown(0);\n };\n\n const onSigTerm = () => {\n void shutdown(0);\n };\n\n const removeSignalHandlers = (): void => {\n process.off(\"SIGINT\", onSigInt);\n process.off(\"SIGTERM\", onSigTerm);\n };\n\n const close = async (): Promise<undefined> => {\n removeSignalHandlers();\n return resolvedApp.close();\n };\n\n const startedApp: Pick<FastifyInstance, \"listen\" | \"close\"> = {\n listen: resolvedApp.listen.bind(resolvedApp),\n close: close as FastifyInstance[\"close\"],\n };\n\n const shutdown = async (code: number): Promise<void> => {\n try {\n await close();\n process.exit(code);\n } catch (error) {\n console.error(\"Failed to shutdown microsandbox-service cleanly\", error);\n process.exit(1);\n }\n };\n\n process.once(\"SIGINT\", onSigInt);\n process.once(\"SIGTERM\", onSigTerm);\n\n try {\n await resolvedApp.listen({ port: config.port, host: config.host });\n return startedApp;\n } catch (error) {\n removeSignalHandlers();\n throw error;\n }\n}\n"],"mappings":";;;;;AAWA,SAAS,UAAU,KAAa,QAAwB;AACtD,QAAM,QAAQ,OAAO,GAAG;AACxB,MAAI,CAAC,OAAO,UAAU,KAAK,KAAK,SAAS,KAAK,QAAQ,OAAO;AAC3D,UAAM,IAAI,MAAM,qBAAqB,MAAM,KAAK,GAAG,EAAE;AAAA,EACvD;AACA,SAAO;AACT;AAEA,SAAS,qBAAqB,KAA2C;AACvE,SAAO,CAAC,OAAO,IAAI,WAAW,IAAI;AACpC;AAEO,SAAS,gBAAgB,MAAkC;AAChE,QAAM,SAA2B,CAAC;AAElC,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,QAAQ,KAAK,KAAK;AAExB,QAAI,UAAU,UAAU;AACtB,YAAM,MAAM,KAAK,QAAQ,CAAC;AAC1B,UAAI,qBAAqB,GAAG,GAAG;AAC7B,cAAM,IAAI,MAAM,0BAA0B;AAAA,MAC5C;AACA,aAAO,OAAO,UAAU,KAAK,QAAQ;AACrC,eAAS;AACT;AAAA,IACF;AAEA,QAAI,UAAU,UAAU;AACtB,YAAM,MAAM,KAAK,QAAQ,CAAC;AAC1B,UAAI,qBAAqB,GAAG,GAAG;AAC7B,cAAM,IAAI,MAAM,0BAA0B;AAAA,MAC5C;AACA,aAAO,OAAO;AACd,eAAS;AAAA,IACX;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAGyB;AACvB,SAAO;AAAA,IACL,MAAM,KAAK,QAAQ,IAAI,QAAQ;AAAA,IAC/B,MAAM,KAAK,SAAS,IAAI,OAAO,UAAU,IAAI,MAAM,MAAM,IAAI;AAAA,IAC7D,QAAQ,IAAI;AAAA,EACd;AACF;;;AC5DA,eAAsB,YAAY;AAAA,EAChC,OAAO,QAAQ,KAAK,MAAM,CAAC;AAAA,EAC3B,MAAM,QAAQ;AAAA,EACd;AACF,IAII,CAAC,GAAuD;AAC1D,QAAM,SAAS,oBAAoB,EAAE,MAAM,gBAAgB,IAAI,GAAG,IAAI,CAAC;AACvE,QAAM,cAAc,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAO,CAAC;AAE7D,QAAM,WAAW,MAAM;AACrB,SAAK,SAAS,CAAC;AAAA,EACjB;AAEA,QAAM,YAAY,MAAM;AACtB,SAAK,SAAS,CAAC;AAAA,EACjB;AAEA,QAAM,uBAAuB,MAAY;AACvC,YAAQ,IAAI,UAAU,QAAQ;AAC9B,YAAQ,IAAI,WAAW,SAAS;AAAA,EAClC;AAEA,QAAM,QAAQ,YAAgC;AAC5C,yBAAqB;AACrB,WAAO,YAAY,MAAM;AAAA,EAC3B;AAEA,QAAM,aAAwD;AAAA,IAC5D,QAAQ,YAAY,OAAO,KAAK,WAAW;AAAA,IAC3C;AAAA,EACF;AAEA,QAAM,WAAW,OAAO,SAAgC;AACtD,QAAI;AACF,YAAM,MAAM;AACZ,cAAQ,KAAK,IAAI;AAAA,IACnB,SAAS,OAAO;AACd,cAAQ,MAAM,mDAAmD,KAAK;AACtE,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAEA,UAAQ,KAAK,UAAU,QAAQ;AAC/B,UAAQ,KAAK,WAAW,SAAS;AAEjC,MAAI;AACF,UAAM,YAAY,OAAO,EAAE,MAAM,OAAO,MAAM,MAAM,OAAO,KAAK,CAAC;AACjE,WAAO;AAAA,EACT,SAAS,OAAO;AACd,yBAAqB;AACrB,UAAM;AAAA,EACR;AACF;","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -493,6 +493,22 @@ var MicrosandboxRuntimeService = class {
|
|
|
493
493
|
}
|
|
494
494
|
return import_microsandbox2.Mount.tmpfs({ sizeMib: definition.sizeMib });
|
|
495
495
|
}
|
|
496
|
+
async ensureNamedVolumes(volumes) {
|
|
497
|
+
if (!volumes) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const defaultQuotaMib = Number(process.env.MICROSANDBOX_VOLUME_QUOTA_MIB ?? "1024");
|
|
501
|
+
const namedVolumes = Object.values(volumes).filter((v) => v.type === "named");
|
|
502
|
+
await Promise.all(
|
|
503
|
+
namedVolumes.map(async (definition) => {
|
|
504
|
+
try {
|
|
505
|
+
await import_microsandbox2.Volume.get(definition.name);
|
|
506
|
+
} catch {
|
|
507
|
+
await import_microsandbox2.Volume.create({ name: definition.name, quotaMib: defaultQuotaMib });
|
|
508
|
+
}
|
|
509
|
+
})
|
|
510
|
+
);
|
|
511
|
+
}
|
|
496
512
|
getNative(name) {
|
|
497
513
|
return this.registry.get(name);
|
|
498
514
|
}
|
|
@@ -507,6 +523,7 @@ var MicrosandboxRuntimeService = class {
|
|
|
507
523
|
const image = input.image ?? process.env.MICROSANDBOX_IMAGE ?? "python:3.11-slim";
|
|
508
524
|
const cpus = input.cpus ?? Number(process.env.MICROSANDBOX_CPUS ?? "1");
|
|
509
525
|
const memoryMib = input.memoryMib ?? Number(process.env.MICROSANDBOX_MEMORY ?? "512");
|
|
526
|
+
await this.ensureNamedVolumes(input.volumes);
|
|
510
527
|
await this.registry.ensure(name, {
|
|
511
528
|
image,
|
|
512
529
|
cpus,
|
|
@@ -681,12 +698,37 @@ var MicrosandboxRuntimeService = class {
|
|
|
681
698
|
// src/app.ts
|
|
682
699
|
function buildApp({
|
|
683
700
|
runtimeService = new MicrosandboxRuntimeService(),
|
|
684
|
-
imageService = new ImageService()
|
|
701
|
+
imageService = new ImageService(),
|
|
702
|
+
apiKey
|
|
685
703
|
} = {}) {
|
|
686
704
|
const app = (0, import_fastify.default)({ logger: false });
|
|
687
|
-
app.register(import_cors.default, {
|
|
705
|
+
app.register(import_cors.default, {
|
|
706
|
+
delegator: (request, callback) => {
|
|
707
|
+
callback(null, {
|
|
708
|
+
origin: true,
|
|
709
|
+
methods: request.headers["access-control-request-method"] ?? "*",
|
|
710
|
+
allowedHeaders: request.headers["access-control-request-headers"]
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
});
|
|
688
714
|
app.register(import_sensible.default);
|
|
689
715
|
app.register(import_multipart.default);
|
|
716
|
+
if (apiKey) {
|
|
717
|
+
app.addHook("onRequest", async (request, reply) => {
|
|
718
|
+
if (request.method === "OPTIONS" || !request.url.startsWith("/api/")) {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
if (request.headers.authorization !== `Bearer ${apiKey}`) {
|
|
722
|
+
await reply.code(401).send({
|
|
723
|
+
success: false,
|
|
724
|
+
error: {
|
|
725
|
+
code: "UNAUTHORIZED",
|
|
726
|
+
message: "Unauthorized"
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
}
|
|
690
732
|
registerHealthRoutes(app);
|
|
691
733
|
registerSandboxRoutes(app, runtimeService);
|
|
692
734
|
registerImageRoutes(app, imageService);
|
|
@@ -700,18 +742,21 @@ function buildApp({
|
|
|
700
742
|
// src/lib/server-cli.ts
|
|
701
743
|
function parsePort(raw, source) {
|
|
702
744
|
const value = Number(raw);
|
|
703
|
-
if (!Number.isInteger(value) || value <= 0) {
|
|
745
|
+
if (!Number.isInteger(value) || value <= 0 || value > 65535) {
|
|
704
746
|
throw new Error(`Invalid value for ${source}: ${raw}`);
|
|
705
747
|
}
|
|
706
748
|
return value;
|
|
707
749
|
}
|
|
750
|
+
function isMissingOptionValue(raw) {
|
|
751
|
+
return !raw || raw.startsWith("--");
|
|
752
|
+
}
|
|
708
753
|
function parseServerArgs(argv) {
|
|
709
754
|
const parsed = {};
|
|
710
755
|
for (let index = 0; index < argv.length; index += 1) {
|
|
711
756
|
const token = argv[index];
|
|
712
757
|
if (token === "--port") {
|
|
713
758
|
const raw = argv[index + 1];
|
|
714
|
-
if (
|
|
759
|
+
if (isMissingOptionValue(raw)) {
|
|
715
760
|
throw new Error("Missing value for --port");
|
|
716
761
|
}
|
|
717
762
|
parsed.port = parsePort(raw, "--port");
|
|
@@ -720,7 +765,7 @@ function parseServerArgs(argv) {
|
|
|
720
765
|
}
|
|
721
766
|
if (token === "--host") {
|
|
722
767
|
const raw = argv[index + 1];
|
|
723
|
-
if (
|
|
768
|
+
if (isMissingOptionValue(raw)) {
|
|
724
769
|
throw new Error("Missing value for --host");
|
|
725
770
|
}
|
|
726
771
|
parsed.host = raw;
|
|
@@ -735,7 +780,8 @@ function resolveServerConfig({
|
|
|
735
780
|
}) {
|
|
736
781
|
return {
|
|
737
782
|
host: args.host ?? env.HOST ?? "0.0.0.0",
|
|
738
|
-
port: args.port ?? (env.PORT ? parsePort(env.PORT, "PORT") : 4002)
|
|
783
|
+
port: args.port ?? (env.PORT ? parsePort(env.PORT, "PORT") : 4002),
|
|
784
|
+
apiKey: env.MICROSANDBOX_API_KEY
|
|
739
785
|
};
|
|
740
786
|
}
|
|
741
787
|
|
|
@@ -743,26 +789,46 @@ function resolveServerConfig({
|
|
|
743
789
|
async function startServer({
|
|
744
790
|
argv = process.argv.slice(2),
|
|
745
791
|
env = process.env,
|
|
746
|
-
app
|
|
792
|
+
app
|
|
747
793
|
} = {}) {
|
|
748
794
|
const config = resolveServerConfig({ args: parseServerArgs(argv), env });
|
|
795
|
+
const resolvedApp = app ?? buildApp({ apiKey: config.apiKey });
|
|
796
|
+
const onSigInt = () => {
|
|
797
|
+
void shutdown(0);
|
|
798
|
+
};
|
|
799
|
+
const onSigTerm = () => {
|
|
800
|
+
void shutdown(0);
|
|
801
|
+
};
|
|
802
|
+
const removeSignalHandlers = () => {
|
|
803
|
+
process.off("SIGINT", onSigInt);
|
|
804
|
+
process.off("SIGTERM", onSigTerm);
|
|
805
|
+
};
|
|
806
|
+
const close = async () => {
|
|
807
|
+
removeSignalHandlers();
|
|
808
|
+
return resolvedApp.close();
|
|
809
|
+
};
|
|
810
|
+
const startedApp = {
|
|
811
|
+
listen: resolvedApp.listen.bind(resolvedApp),
|
|
812
|
+
close
|
|
813
|
+
};
|
|
749
814
|
const shutdown = async (code) => {
|
|
750
815
|
try {
|
|
751
|
-
await
|
|
816
|
+
await close();
|
|
752
817
|
process.exit(code);
|
|
753
818
|
} catch (error) {
|
|
754
819
|
console.error("Failed to shutdown microsandbox-service cleanly", error);
|
|
755
820
|
process.exit(1);
|
|
756
821
|
}
|
|
757
822
|
};
|
|
758
|
-
process.once("SIGINT",
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
})
|
|
764
|
-
|
|
765
|
-
|
|
823
|
+
process.once("SIGINT", onSigInt);
|
|
824
|
+
process.once("SIGTERM", onSigTerm);
|
|
825
|
+
try {
|
|
826
|
+
await resolvedApp.listen({ port: config.port, host: config.host });
|
|
827
|
+
return startedApp;
|
|
828
|
+
} catch (error) {
|
|
829
|
+
removeSignalHandlers();
|
|
830
|
+
throw error;
|
|
831
|
+
}
|
|
766
832
|
}
|
|
767
833
|
|
|
768
834
|
// src/cli.ts
|