apps/sandbox/alchemy.run.ts
Rama main · réplica de lectura del repositorio original.
/**
* The sandbox Worker's declaration, kept out of `src/worker.ts` so that module stays
* a plain bundle entry and its `Sandbox` class export survives into the script.
*/
import {
assetWorkerObservability,
MapleStack,
mapleWorkerProps,
REGION_INFRA,
resolveWorkerName,
WorkersObservabilityDestinations,
} from "@maple/infra/cloudflare"
import { requireSecretEntry } from "@maple/infra/env"
import * as Cloudflare from "@alchemy.run/cloudflare"
import { Effect } from "effect"
import type { Executor, Sandbox } from "./src/worker.ts"
/**
* The container image, built here and pushed to the account registry on a content change. Its
* `sandbox-shim` tag must match the `@cloudflare/sandbox` version in `package.json`.
*/
const SANDBOX_IMAGE_CONTEXT = `${import.meta.dirname}/image`
/**
* Git mirror archives between container lifetimes. Customer source: follows the storage
* jurisdiction, and the lifecycle rule is the only thing that deletes an archive, so it bounds how
* long a disconnected repository's source stays in R2. Daily refreshes keep an active one fresh.
* The container never holds credentials for it: `DirectoryBackupGateway` moves each archive
* through the Worker.
*/
const mirrorBackups = Effect.gen(function* () {
const { stage, region } = yield* MapleStack
const bucketName = resolveWorkerName("sandbox-mirrors", stage, region)
const jurisdiction = REGION_INFRA[region].storageJurisdiction
const bucket = yield* Cloudflare.R2.Bucket("sandbox-mirrors", {
name: bucketName,
jurisdiction,
lifecycleRules: [
{
id: "expire-mirror-backups",
enabled: true,
deleteObjectsTransition: { condition: { type: "Age", maxAge: 8 * 24 * 60 * 60 } },
// A container sleeping mid-upload leaves parts object expiry never sees.
abortMultipartUploadsTransition: { condition: { type: "Age", maxAge: 24 * 60 * 60 } },
},
],
// A cache: every archive can be rebuilt by one clone, so a teardown may empty it.
forceDestroy: true,
})
return { BACKUP_BUCKET: bucket }
})
const props = Effect.gen(function* () {
if (globalThis.__ALCHEMY_RUNTIME__) return { main: `${import.meta.dirname}/src/worker.ts` }
const stack = yield* MapleStack
const { instanceType, maxInstances, executorMaxInstances } = stack.profile.sandbox
// No OTel SDK in this Worker, so platform logs are its only telemetry. Keep them on.
const destinations = yield* WorkersObservabilityDestinations
return {
main: `${import.meta.dirname}/src/worker.ts`,
...mapleWorkerProps("sandbox", stack),
// Reached only over a service binding: no route, no hostname.
workersDev: false,
// Hourly, and a no-op once the current probe version has reported.
crons: ["23 * * * *"],
observability: assetWorkerObservability(destinations),
env: {
// Fleet scheduling on purpose: `durable_object` has no `maxInstances` cap, and
// switching policy needs a new application and Durable Object namespace.
Sandbox: Cloudflare.Container<Sandbox>("Sandbox", {
context: SANDBOX_IMAGE_CONTEXT,
instanceType,
maxInstances,
observability: { logs: { enabled: true } },
}),
// The execution sandbox: started with internet off, one per security audit run (and the
// platform probe on the cron below). Probe v2: standard-2 is 1 vCPU, 6 GB, 11 GB disk.
Executor: Cloudflare.Container<Executor>("Executor", {
context: SANDBOX_IMAGE_CONTEXT,
instanceType: "standard-2" as const,
maxInstances: executorMaxInstances,
observability: { logs: { enabled: true } },
}),
// Never the shared `INTERNAL_SERVICE_TOKEN` (acts as any org; this runs model-chosen
// commands). Required so a missing token fails the deploy, not every call.
...(yield* requireSecretEntry("SANDBOX_INTERNAL_SERVICE_TOKEN")),
...(yield* mirrorBackups),
},
}
})
export default class MapleSandbox extends Cloudflare.Worker<MapleSandbox>()("sandbox", props) {}