Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions .changeset/add-debounce-maxdelay.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/calm-hooks-wait.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/consistent-stream-targets.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/export-start-attempt-hook-type.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/fix-dead-process-execute-hang.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/mcp-wait-timeout.md

This file was deleted.

13 changes: 0 additions & 13 deletions .changeset/selfish-cooks-sparkle.md

This file was deleted.

7 changes: 0 additions & 7 deletions .changeset/vendor-superjson-esm-fix.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/vercel-integration.md

This file was deleted.

3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
postgres-data
# dependencies
node_modules
.pnpm-store/
.pnp
.pnp.js

Expand Down Expand Up @@ -67,4 +68,4 @@ apps/**/public/build
**/.claude/settings.local.json
.mcp.log
.mcp.json
.cursor/debug.log
.cursor/debug.log
5 changes: 5 additions & 0 deletions apps/supervisor/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ const Env = z.object({
KUBERNETES_FORCE_ENABLED: BoolEnv.default(false),
KUBERNETES_NAMESPACE: z.string().default("default"),
KUBERNETES_WORKER_NODETYPE_LABEL: z.string().default("v4-worker"),
KUBERNETES_RUNNER_ENV_SECRET_MOUNT_ENABLED: BoolEnv.default(false),
KUBERNETES_RUNNER_ENV_SECRET_NAME_PREFIX: z.string().default("runner-env"),
KUBERNETES_RUNNER_ENV_SECRET_KEY: z.string().default("secrets.env"),
KUBERNETES_RUNNER_ENV_SECRET_MOUNT_PATH: z.string().default("/var/run/secrets/runner-env"),
KUBERNETES_RUNNER_ENV_SECRET_OPTIONAL: BoolEnv.default(false),
KUBERNETES_IMAGE_PULL_SECRETS: z.string().optional(), // csv
KUBERNETES_EPHEMERAL_STORAGE_SIZE_LIMIT: z.string().default("10Gi"),
KUBERNETES_EPHEMERAL_STORAGE_SIZE_REQUEST: z.string().default("2Gi"),
Expand Down
5 changes: 5 additions & 0 deletions apps/supervisor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ class ManagedSupervisor {
snapshotPollIntervalSeconds: env.RUNNER_SNAPSHOT_POLL_INTERVAL_SECONDS,
additionalEnvVars: env.RUNNER_ADDITIONAL_ENV_VARS,
dockerAutoremove: env.DOCKER_AUTOREMOVE_EXITED_CONTAINERS,
runnerEnvSecretMountEnabled: env.KUBERNETES_RUNNER_ENV_SECRET_MOUNT_ENABLED,
runnerEnvSecretNamePrefix: env.KUBERNETES_RUNNER_ENV_SECRET_NAME_PREFIX,
runnerEnvSecretKey: env.KUBERNETES_RUNNER_ENV_SECRET_KEY,
runnerEnvSecretMountPath: env.KUBERNETES_RUNNER_ENV_SECRET_MOUNT_PATH,
runnerEnvSecretOptional: env.KUBERNETES_RUNNER_ENV_SECRET_OPTIONAL,
} satisfies WorkloadManagerOptions;

this.resourceMonitor = env.RESOURCE_MONITOR_ENABLED
Expand Down
69 changes: 68 additions & 1 deletion apps/supervisor/src/workloadManager/kubernetes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ export class KubernetesWorkloadManager implements WorkloadManager {
this.logger.log("[KubernetesWorkloadManager] Creating container", { opts });

const runnerId = getRunnerId(opts.runFriendlyId, opts.nextAttemptNumber);
const runnerEnvSecretMount = this.#getRunnerEnvSecretMount(opts);
const basePodSpec = this.addPlacementTags(this.#defaultPodSpec, opts.placementTags);

try {
await this.k8s.core.createNamespacedPod({
Expand All @@ -119,9 +121,12 @@ export class KubernetesWorkloadManager implements WorkloadManager {
},
},
spec: {
...this.addPlacementTags(this.#defaultPodSpec, opts.placementTags),
...basePodSpec,
affinity: this.#getAffinity(opts.machine, opts.projectId),
terminationGracePeriodSeconds: 60 * 60,
...(runnerEnvSecretMount
? { volumes: [...(basePodSpec.volumes ?? []), runnerEnvSecretMount.volume] }
: {}),
containers: [
{
name: "run-controller",
Expand All @@ -132,6 +137,17 @@ export class KubernetesWorkloadManager implements WorkloadManager {
},
],
resources: this.#getResourcesForMachine(opts.machine),
...(runnerEnvSecretMount
? {
volumeMounts: [
{
name: runnerEnvSecretMount.volume.name,
mountPath: runnerEnvSecretMount.mountPath,
readOnly: true,
},
],
}
: {}),
env: [
{
name: "TRIGGER_DEQUEUED_AT_MS",
Expand Down Expand Up @@ -245,6 +261,14 @@ export class KubernetesWorkloadManager implements WorkloadManager {
},
]
: []),
...(runnerEnvSecretMount
? [
{
name: "TRIGGER_MOUNTED_ENV_FILE",
value: runnerEnvSecretMount.filePath,
},
]
: []),
...(this.opts.additionalEnvVars
? Object.entries(this.opts.additionalEnvVars).map(([key, value]) => ({
name: key,
Expand Down Expand Up @@ -299,6 +323,49 @@ export class KubernetesWorkloadManager implements WorkloadManager {
}
}

#getRunnerEnvSecretMount(
opts: WorkloadManagerCreateOptions
):
| {
mountPath: string;
filePath: string;
volume: k8s.V1Volume;
}
| undefined {
if (!this.opts.runnerEnvSecretMountEnabled) {
return undefined;
}

const prefix = this.opts.runnerEnvSecretNamePrefix?.trim();
const secretKey = this.opts.runnerEnvSecretKey?.trim();
const rawMountPath = this.opts.runnerEnvSecretMountPath?.trim();

if (!prefix || !secretKey || !rawMountPath) {
this.logger.warn("[KubernetesWorkloadManager] Runner env secret mount is enabled but invalid");
return undefined;
}

const envLabel = this.#envTypeToLabelValue(opts.envType);
const fileName = `${envLabel}.env`;
const mountPath = rawMountPath.replace(/\/+$/, "");
const filePath = `${mountPath}/${fileName}`;
const secretName = `${prefix}-${envLabel}`;
const volumeName = "runner-env-secret";

return {
mountPath,
filePath,
volume: {
name: volumeName,
secret: {
secretName,
optional: this.opts.runnerEnvSecretOptional,
items: [{ key: secretKey, path: fileName }],
},
},
};
}

private getImagePullSecrets(): k8s.V1LocalObjectReference[] | undefined {
return this.opts.imagePullSecrets?.map((name) => ({ name }));
}
Expand Down
5 changes: 5 additions & 0 deletions apps/supervisor/src/workloadManager/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export interface WorkloadManagerOptions {
snapshotPollIntervalSeconds?: number;
additionalEnvVars?: Record<string, string>;
dockerAutoremove?: boolean;
runnerEnvSecretMountEnabled?: boolean;
runnerEnvSecretNamePrefix?: string;
runnerEnvSecretKey?: string;
runnerEnvSecretMountPath?: string;
runnerEnvSecretOptional?: boolean;
}

export interface WorkloadManager {
Expand Down
8 changes: 8 additions & 0 deletions packages/build/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @basicblock/trigger-build

## 4.3.4

### Patch Changes

- ok ([#5](https://github.com/BasicBlock/trigger.dev/pull/5))
- Updated dependencies:
- `@basicblock/trigger-core@4.3.4`

## 4.3.3

### Patch Changes
Expand Down
4 changes: 2 additions & 2 deletions packages/build/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@basicblock/trigger-build",
"version": "4.3.3",
"version": "4.3.4",
"description": "trigger.dev build extensions",
"license": "MIT",
"publishConfig": {
Expand Down Expand Up @@ -79,7 +79,7 @@
},
"dependencies": {
"@prisma/config": "^6.10.0",
"@basicblock/trigger-core": "workspace:4.3.3",
"@basicblock/trigger-core": "workspace:4.3.4",
"mlly": "^1.7.1",
"pkg-types": "^1.1.3",
"resolve": "^1.22.8",
Expand Down
12 changes: 12 additions & 0 deletions packages/cli-v3/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# trigger.dev

## 4.3.4

### Patch Changes

- Fix runner getting stuck indefinitely when `execute()` is called on a dead child process. ([#2978](https://github.com/triggerdotdev/trigger.dev/pull/2978))
- Add optional `timeoutInSeconds` parameter to the `wait_for_run_to_complete` MCP tool. Defaults to 60 seconds. If the run doesn't complete within the timeout, the current state of the run is returned instead of waiting indefinitely. ([#3035](https://github.com/triggerdotdev/trigger.dev/pull/3035))
- ok ([#5](https://github.com/BasicBlock/trigger.dev/pull/5))
- Updated dependencies:
- `@basicblock/trigger-core@4.3.4`
- `@basicblock/trigger-schema-to-json@4.3.4`
- `@basicblock/trigger-build@4.3.4`

## 4.3.3

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/cli-v3/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ If you want to use it in a new folder, you need to first add it as a dev depende
```json
//...
"devDependencies": {
"trigger.dev": "workspace:*",
"@basicblock/trigger-cli": "workspace:*",
//...
}
//...
Expand Down
8 changes: 4 additions & 4 deletions packages/cli-v3/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@basicblock/trigger-cli",
"version": "4.3.3",
"version": "4.3.4",
"description": "A Command-Line Interface for Trigger.dev projects",
"type": "module",
"license": "MIT",
Expand Down Expand Up @@ -94,9 +94,9 @@
"@opentelemetry/sdk-trace-node": "2.0.1",
"@opentelemetry/semantic-conventions": "1.36.0",
"@s2-dev/streamstore": "^0.17.6",
"@basicblock/trigger-build": "workspace:4.3.3",
"@basicblock/trigger-core": "workspace:4.3.3",
"@basicblock/trigger-schema-to-json": "workspace:4.3.3",
"@basicblock/trigger-build": "workspace:4.3.4",
"@basicblock/trigger-core": "workspace:4.3.4",
"@basicblock/trigger-schema-to-json": "workspace:4.3.4",
"ansi-escapes": "^7.0.0",
"braces": "^3.0.3",
"c12": "^1.11.1",
Expand Down
59 changes: 59 additions & 0 deletions packages/cli-v3/src/entryPoints/managed-run-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,63 @@ sourceMapSupport.install({
hookRequire: false,
});

function parseEnvFile(contents: string): Record<string, string> {
return contents.split(/\r?\n/).reduce(
(acc, line) => {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) {
return acc;
}

const delimiterIndex = trimmed.indexOf("=");
if (delimiterIndex === -1) {
return acc;
}

const key = trimmed.slice(0, delimiterIndex).trim();
const value = trimmed.slice(delimiterIndex + 1).trim();

if (!key) {
return acc;
}

const unquotedValue =
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
? value.slice(1, -1)
: value;

acc[key] = unquotedValue;
return acc;
},
{} as Record<string, string>
);
}

async function loadMountedEnvFile() {
const mountedEnvPath = process.env.TRIGGER_MOUNTED_ENV_FILE ?? process.env.DOPPLER_SECRETS_FILE;
if (!mountedEnvPath) {
return;
}

try {
const envContents = await readFile(mountedEnvPath, "utf8");
const parsedEnv = parseEnvFile(envContents);

for (const [key, value] of Object.entries(parsedEnv)) {
// Keep explicit process env values intact (including Trigger-managed vars).
if (process.env[key] === undefined) {
process.env[key] = value;
}
}
} catch (error) {
console.error("Failed to load mounted env file", {
mountedEnvPath,
error: error instanceof Error ? error.message : String(error),
});
}
}

process.on("uncaughtException", function (error, origin) {
console.error("Uncaught exception", { error, origin });
if (error instanceof Error) {
Expand Down Expand Up @@ -109,6 +166,8 @@ process.on("uncaughtException", function (error, origin) {
}
});

await loadMountedEnvFile();

const heartbeatIntervalMs = getEnvVar("HEARTBEAT_INTERVAL_MS");

const standardLocalsManager = new StandardLocalsManager();
Expand Down
Loading
Loading