Skip to content

Commit 1f19a8c

Browse files
committed
Merge remote-tracking branch 'origin/main' into v4/self-hosting
2 parents 4cf8a55 + f603725 commit 1f19a8c

File tree

51 files changed

+1058
-999
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1058
-999
lines changed

.changeset/flat-pianos-live.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"trigger.dev": patch
3+
"@trigger.dev/core": patch
4+
---
5+
6+
Runtime agnostic SDK config via env vars

.changeset/ninety-games-grow.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"trigger.dev": patch
3+
"@trigger.dev/core": patch
4+
---
5+
6+
- Resolve issue where CLI could get stuck during deploy finalization
7+
- Unify local and remote build logic, with multi-platform build support
8+
- Improve switch command; now accepts profile name as an argument
9+
- Registry configuration is now fully managed by the webapp
10+
- The deploy `--self-hosted` flag is no longer required
11+
- Enhance deployment error reporting and image digest retrieval

.env.example

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,9 @@ CLOUD_SLACK_CLIENT_SECRET=
8080
PROVIDER_SECRET=provider-secret # generate the actual secret with `openssl rand -hex 32`
8181
COORDINATOR_SECRET=coordinator-secret # generate the actual secret with `openssl rand -hex 32`
8282

83-
# Uncomment the following line to enable the registry proxy
84-
# ENABLE_REGISTRY_PROXY=true
83+
# DEPOT_ORG_ID=<Depot org id>
8584
# DEPOT_TOKEN=<Depot org token>
86-
# DEPOT_PROJECT_ID=<Depot project id>
87-
# DEPLOY_REGISTRY_HOST=${APP_ORIGIN} # This is the host that the deploy CLI will use to push images to the registry
88-
# CONTAINER_REGISTRY_ORIGIN=<Container registry origin e.g. https://registry.digitalocean.com>
89-
# CONTAINER_REGISTRY_USERNAME=<Container registry username e.g. Digital ocean email address>
90-
# CONTAINER_REGISTRY_PASSWORD=<Container registry password e.g. Digital ocean PAT>
85+
DEPLOY_REGISTRY_HOST=${APP_ORIGIN} # This is the host that the deploy CLI will use to push images to the registry
9186
# DEV_OTEL_EXPORTER_OTLP_ENDPOINT="http://0.0.0.0:4318"
9287
# These are needed for the object store (for handling large payloads/outputs)
9388
# OBJECT_STORE_BASE_URL="https://{bucket}.{accountId}.r2.cloudflarestorage.com"

.github/workflows/publish-webapp.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,17 @@ jobs:
5656
5757
echo "image_tags=${image_tags}" >> "$GITHUB_OUTPUT"
5858
59+
- name: 📝 Set the build info
60+
id: set_build_info
61+
run: |
62+
tag=${{ steps.get_tag.outputs.tag }}
63+
if [[ "${{ steps.get_tag.outputs.is_semver }}" == true ]]; then
64+
echo "BUILD_APP_VERSION=${tag}" >> "$GITHUB_OUTPUT"
65+
fi
66+
echo "BUILD_GIT_SHA=${{ github.sha }}" >> "$GITHUB_OUTPUT"
67+
echo "BUILD_GIT_REF_NAME=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
68+
echo "BUILD_TIMESTAMP_SECONDS=$(date +%s)" >> "$GITHUB_OUTPUT"
69+
5970
- name: 🐙 Login to GitHub Container Registry
6071
uses: docker/login-action@v3
6172
with:
@@ -70,3 +81,8 @@ jobs:
7081
platforms: linux/amd64,linux/arm64
7182
tags: ${{ steps.set_tags.outputs.image_tags }}
7283
push: true
84+
build-args: |
85+
BUILD_APP_VERSION=${{ steps.set_build_info.outputs.BUILD_APP_VERSION }}
86+
BUILD_GIT_SHA=${{ steps.set_build_info.outputs.BUILD_GIT_SHA }}
87+
BUILD_GIT_REF_NAME=${{ steps.set_build_info.outputs.BUILD_GIT_REF_NAME }}
88+
BUILD_TIMESTAMP_SECONDS=${{ steps.set_build_info.outputs.BUILD_TIMESTAMP_SECONDS }}

.github/workflows/unit-tests-internal.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,4 @@ jobs:
127127
merge-multiple: true
128128

129129
- name: Merge reports
130-
run: pnpm dlx [email protected] run --merge-reports
130+
run: pnpm dlx [email protected] run --merge-reports --pass-with-no-tests

.github/workflows/unit-tests-packages.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,4 @@ jobs:
127127
merge-multiple: true
128128

129129
- name: Merge reports
130-
run: pnpm dlx [email protected] run --merge-reports
130+
run: pnpm dlx [email protected] run --merge-reports --pass-with-no-tests

.github/workflows/unit-tests-webapp.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ jobs:
8686
SESSION_SECRET: "secret"
8787
MAGIC_LINK_SECRET: "secret"
8888
ENCRYPTION_KEY: "secret"
89+
DEPLOY_REGISTRY_HOST: "docker.io"
8990

9091
- name: Gather all reports
9192
if: ${{ !cancelled() }}
@@ -133,4 +134,4 @@ jobs:
133134
merge-multiple: true
134135

135136
- name: Merge reports
136-
run: pnpm dlx [email protected] run --merge-reports
137+
run: pnpm dlx [email protected] run --merge-reports --pass-with-no-tests

apps/supervisor/src/env.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ const Env = z.object({
2929
RUNNER_HEARTBEAT_INTERVAL_SECONDS: z.coerce.number().optional(),
3030
RUNNER_SNAPSHOT_POLL_INTERVAL_SECONDS: z.coerce.number().optional(),
3131
RUNNER_ADDITIONAL_ENV_VARS: AdditionalEnvVars, // optional (csv)
32+
RUNNER_PRETTY_LOGS: BoolEnv.default(false),
3233

33-
// Dequeue settings
34+
// Dequeue settings (provider mode)
3435
TRIGGER_DEQUEUE_ENABLED: BoolEnv.default("true"),
3536
TRIGGER_DEQUEUE_INTERVAL_MS: z.coerce.number().int().default(250),
3637
TRIGGER_DEQUEUE_IDLE_INTERVAL_MS: z.coerce.number().int().default(1000),
@@ -48,6 +49,12 @@ const Env = z.object({
4849
RESOURCE_MONITOR_OVERRIDE_MEMORY_TOTAL_GB: z.coerce.number().optional(),
4950

5051
// Docker settings
52+
DOCKER_API_VERSION: z.string().default("v1.41"),
53+
DOCKER_PLATFORM: z.string().optional(), // e.g. linux/amd64, linux/arm64
54+
DOCKER_STRIP_IMAGE_DIGEST: BoolEnv.default(true),
55+
DOCKER_REGISTRY_USERNAME: z.string().optional(),
56+
DOCKER_REGISTRY_PASSWORD: z.string().optional(),
57+
DOCKER_REGISTRY_URL: z.string().optional(), // e.g. https://index.docker.io/v1
5158
DOCKER_ENFORCE_MACHINE_PRESETS: BoolEnv.default(true),
5259
DOCKER_AUTOREMOVE_EXITED_CONTAINERS: BoolEnv.default(true),
5360
/**

apps/supervisor/src/util.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
export function getDockerHostDomain() {
2-
const isMacOs = process.platform === "darwin";
3-
const isWindows = process.platform === "win32";
1+
import { isMacOS, isWindows } from "std-env";
42

5-
return isMacOs || isWindows ? "host.docker.internal" : "localhost";
3+
export function getDockerHostDomain() {
4+
return isMacOS || isWindows ? "host.docker.internal" : "localhost";
65
}
76

87
export function getRunnerId(runId: string, attemptNumber?: number) {

apps/supervisor/src/workloadManager/docker.ts

Lines changed: 118 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@ export class DockerWorkloadManager implements WorkloadManager {
1414
private readonly docker: Docker;
1515

1616
private readonly runnerNetworks: string[];
17+
private readonly auth?: Docker.AuthConfig;
18+
private readonly platformOverride?: string;
1719

1820
constructor(private opts: WorkloadManagerOptions) {
19-
this.docker = new Docker();
21+
this.docker = new Docker({
22+
version: env.DOCKER_API_VERSION,
23+
});
2024

2125
if (opts.workloadApiDomain) {
2226
this.logger.warn("⚠️ Custom workload API domain", {
@@ -25,6 +29,29 @@ export class DockerWorkloadManager implements WorkloadManager {
2529
}
2630

2731
this.runnerNetworks = env.DOCKER_RUNNER_NETWORKS.split(",");
32+
33+
this.platformOverride = env.DOCKER_PLATFORM;
34+
if (this.platformOverride) {
35+
this.logger.info("🖥️ Platform override", {
36+
targetPlatform: this.platformOverride,
37+
hostPlatform: process.arch,
38+
});
39+
}
40+
41+
if (env.DOCKER_REGISTRY_USERNAME && env.DOCKER_REGISTRY_PASSWORD && env.DOCKER_REGISTRY_URL) {
42+
this.logger.info("🐋 Using Docker registry credentials", {
43+
username: env.DOCKER_REGISTRY_USERNAME,
44+
url: env.DOCKER_REGISTRY_URL,
45+
});
46+
47+
this.auth = {
48+
username: env.DOCKER_REGISTRY_USERNAME,
49+
password: env.DOCKER_REGISTRY_PASSWORD,
50+
serveraddress: env.DOCKER_REGISTRY_URL,
51+
};
52+
} else {
53+
this.logger.warn("🐋 No Docker registry credentials provided, skipping auth");
54+
}
2855
}
2956

3057
async create(opts: WorkloadManagerCreateOptions) {
@@ -47,6 +74,7 @@ export class DockerWorkloadManager implements WorkloadManager {
4774
`TRIGGER_RUNNER_ID=${runnerId}`,
4875
`TRIGGER_MACHINE_CPU=${opts.machine.cpu}`,
4976
`TRIGGER_MACHINE_MEMORY=${opts.machine.memory}`,
77+
`PRETTY_LOGS=${env.RUNNER_PRETTY_LOGS}`,
5078
];
5179

5280
if (this.opts.warmStartUrl) {
@@ -89,41 +117,103 @@ export class DockerWorkloadManager implements WorkloadManager {
89117
hostConfig.Memory = opts.machine.memory * 1024 * 1024 * 1024;
90118
}
91119

120+
let imageRef = opts.image;
121+
122+
if (env.DOCKER_STRIP_IMAGE_DIGEST) {
123+
imageRef = opts.image.split("@")[0]!;
124+
}
125+
92126
const containerCreateOpts: Docker.ContainerCreateOptions = {
93-
Env: envVars,
94127
name: runnerId,
95128
Hostname: runnerId,
96129
HostConfig: hostConfig,
97-
Image: opts.image,
130+
Image: imageRef,
98131
AttachStdout: false,
99132
AttachStderr: false,
100133
AttachStdin: false,
101134
};
102135

103-
try {
104-
// Create container
105-
const container = await this.docker.createContainer(containerCreateOpts);
136+
if (this.platformOverride) {
137+
containerCreateOpts.platform = this.platformOverride;
138+
}
139+
140+
const logger = this.logger.child({ opts, containerCreateOpts });
141+
142+
const [inspectError, inspectResult] = await tryCatch(this.docker.getImage(imageRef).inspect());
143+
144+
let shouldPull = !!inspectError;
145+
if (this.platformOverride) {
146+
const imageArchitecture = inspectResult?.Architecture;
147+
148+
// When the image architecture doesn't match the platform, we need to pull the image
149+
if (imageArchitecture && !this.platformOverride.includes(imageArchitecture)) {
150+
shouldPull = true;
151+
}
152+
}
153+
154+
// If the image is not present, try to pull it
155+
if (shouldPull) {
156+
logger.info("Pulling image", {
157+
error: inspectError,
158+
image: opts.image,
159+
targetPlatform: this.platformOverride,
160+
imageArchitecture: inspectResult?.Architecture,
161+
});
106162

107-
// If there are multiple networks to attach to we need to attach the remaining ones after creation
108-
if (remainingNetworks.length > 0) {
109-
await this.attachContainerToNetworks({
110-
containerId: container.id,
111-
networkNames: remainingNetworks,
112-
});
163+
// Ensure the image is present
164+
const [createImageError, imageResponseReader] = await tryCatch(
165+
this.docker.createImage(this.auth, {
166+
fromImage: imageRef,
167+
...(this.platformOverride ? { platform: this.platformOverride } : {}),
168+
})
169+
);
170+
if (createImageError) {
171+
logger.error("Failed to pull image", { error: createImageError });
172+
return;
113173
}
114174

115-
// Start container
116-
const startResult = await container.start();
175+
const [imageReadError, imageResponse] = await tryCatch(readAllChunks(imageResponseReader));
176+
if (imageReadError) {
177+
logger.error("failed to read image response", { error: imageReadError });
178+
return;
179+
}
180+
181+
logger.debug("pulled image", { image: opts.image, imageResponse });
182+
} else {
183+
// Image is present, so we can use it to create the container
184+
}
185+
186+
// Create container
187+
const [createContainerError, container] = await tryCatch(
188+
this.docker.createContainer({
189+
...containerCreateOpts,
190+
// Add env vars here so they're not logged
191+
Env: envVars,
192+
})
193+
);
117194

118-
this.logger.debug("create succeeded", {
119-
opts,
120-
startResult,
195+
if (createContainerError) {
196+
logger.error("Failed to create container", { error: createContainerError });
197+
return;
198+
}
199+
200+
// If there are multiple networks to attach to we need to attach the remaining ones after creation
201+
if (remainingNetworks.length > 0) {
202+
await this.attachContainerToNetworks({
121203
containerId: container.id,
122-
containerCreateOpts,
204+
networkNames: remainingNetworks,
123205
});
124-
} catch (error) {
125-
this.logger.error("create failed:", { opts, error, containerCreateOpts });
126206
}
207+
208+
// Start container
209+
const [startError, startResult] = await tryCatch(container.start());
210+
211+
if (startError) {
212+
logger.error("Failed to start container", { error: startError, containerId: container.id });
213+
return;
214+
}
215+
216+
logger.debug("create succeeded", { startResult, containerId: container.id });
127217
}
128218

129219
private async attachContainerToNetworks({
@@ -172,3 +262,11 @@ export class DockerWorkloadManager implements WorkloadManager {
172262
});
173263
}
174264
}
265+
266+
async function readAllChunks(reader: NodeJS.ReadableStream) {
267+
const chunks = [];
268+
for await (const chunk of reader) {
269+
chunks.push(chunk.toString());
270+
}
271+
return chunks;
272+
}

apps/webapp/app/components/GitMetadata.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ export function GitMetadata({ git }: { git?: GitMetaLinks | null }) {
88
return (
99
<>
1010
{git.pullRequestUrl && git.pullRequestNumber && <GitMetadataPullRequest git={git} />}
11-
{git.shortSha && <GitMetadataCommit git={git} />}
1211
{git.branchUrl && <GitMetadataBranch git={git} />}
12+
{git.shortSha && <GitMetadataCommit git={git} />}
1313
</>
1414
);
1515
}

apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,27 @@ import { SideMenuItem } from "./SideMenuItem";
2222
import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route";
2323
import { Paragraph } from "../primitives/Paragraph";
2424
import { Badge } from "../primitives/Badge";
25+
import { useHasAdminAccess } from "~/hooks/useUser";
26+
27+
export type BuildInfo = {
28+
appVersion: string | undefined;
29+
packageVersion: string;
30+
buildTimestampSeconds: string | undefined;
31+
gitSha: string | undefined;
32+
gitRefName: string | undefined;
33+
};
2534

2635
export function OrganizationSettingsSideMenu({
2736
organization,
28-
version,
37+
buildInfo,
2938
}: {
3039
organization: MatchedOrganization;
31-
version: string;
40+
buildInfo: BuildInfo;
3241
}) {
3342
const { isManagedCloud } = useFeatures();
3443
const currentPlan = useCurrentPlan();
44+
const isAdmin = useHasAdminAccess();
45+
const showBuildInfo = isAdmin || !isManagedCloud;
3546

3647
return (
3748
<div
@@ -94,9 +105,33 @@ export function OrganizationSettingsSideMenu({
94105
<div className="flex flex-col gap-1">
95106
<SideMenuHeader title="App version" />
96107
<Paragraph variant="extra-small" className="px-2 text-text-dimmed">
97-
v{version}
108+
{buildInfo.appVersion || `v${buildInfo.packageVersion}`}
98109
</Paragraph>
99110
</div>
111+
{showBuildInfo && buildInfo.buildTimestampSeconds && (
112+
<div className="flex flex-col gap-1">
113+
<SideMenuHeader title="Build timestamp" />
114+
<Paragraph variant="extra-small" className="px-2 text-text-dimmed">
115+
{new Date(Number(buildInfo.buildTimestampSeconds) * 1000).toISOString()}
116+
</Paragraph>
117+
</div>
118+
)}
119+
{showBuildInfo && buildInfo.gitRefName && (
120+
<div className="flex flex-col gap-1">
121+
<SideMenuHeader title="Git ref" />
122+
<Paragraph variant="extra-small" className="px-2 text-text-dimmed">
123+
{buildInfo.gitRefName}
124+
</Paragraph>
125+
</div>
126+
)}
127+
{showBuildInfo && buildInfo.gitSha && (
128+
<div className="flex flex-col gap-1">
129+
<SideMenuHeader title="Git sha" />
130+
<Paragraph variant="extra-small" className="px-2 text-text-dimmed">
131+
{buildInfo.gitSha.slice(0, 9)}
132+
</Paragraph>
133+
</div>
134+
)}
100135
</div>
101136
<div className="flex flex-col gap-1 border-t border-grid-bright p-1">
102137
<HelpAndFeedback />

0 commit comments

Comments
 (0)