Skip to content

Commit 62fbd4f

Browse files
authored
Merge branch 'release' into main
Signed-off-by: Mariano Fuentes <[email protected]>
2 parents 24df405 + e0db103 commit 62fbd4f

File tree

4 files changed

+214
-154
lines changed

4 files changed

+214
-154
lines changed

apps/app/src/jobs/tasks/device/create-fleet-label-for-org.ts

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { getFleetInstance } from '@/lib/fleet';
22
import { db } from '@db';
3-
import { logger, queue, task } from '@trigger.dev/sdk';
43

4+
import { logger, queue, task } from '@trigger.dev/sdk';
5+
import { AxiosError } from 'axios';
56
// Optional: define a queue if we want to control concurrency in v4
67
const fleetQueue = queue({ name: 'create-fleet-label-for-org', concurrencyLimit: 10 });
78

@@ -46,25 +47,73 @@ export const createFleetLabelForOrg = task({
4647

4748
const fleet = await getFleetInstance();
4849

49-
// Create a manual label that we can assign to hosts.
50-
const response = await fleet.post('/labels', {
51-
name: organization.id,
52-
query: query,
53-
});
50+
let labelResponse = null;
5451

55-
logger.info('Label created', {
56-
labelId: response.data.label.id,
57-
});
52+
try {
53+
// Create a manual label that we can assign to hosts.
54+
labelResponse = await fleet.post('/labels', {
55+
name: organization.id,
56+
query: query,
57+
});
5858

59-
// Store label ID in organization.
60-
await db.organization.update({
61-
where: {
62-
id: organizationId,
63-
},
64-
data: {
65-
fleetDmLabelId: response.data.label.id,
66-
},
67-
});
59+
logger.info('Label created', {
60+
labelId: labelResponse.data.label.id,
61+
});
62+
} catch (error) {
63+
if (error instanceof AxiosError && error.response?.status === 409) {
64+
const fleetError = error.response.data;
65+
logger.info('Fleet label already exists for organization', {
66+
organizationId,
67+
httpStatus: error.response.status,
68+
httpStatusText: error.response.statusText,
69+
fleetMessage: fleetError?.message,
70+
fleetErrors: fleetError?.errors,
71+
fleetUuid: fleetError?.uuid,
72+
axiosMessage: error.message,
73+
url: error.config?.url,
74+
method: error.config?.method,
75+
fullResponseData: error.response.data,
76+
});
77+
// Continue with the rest of the flow even if label exists
78+
} else {
79+
const fleetError = error instanceof AxiosError ? error.response?.data : null;
80+
logger.error('Error creating Fleet label', {
81+
organizationId,
82+
httpStatus: error instanceof AxiosError ? error.response?.status : undefined,
83+
httpStatusText: error instanceof AxiosError ? error.response?.statusText : undefined,
84+
fleetMessage: fleetError?.message,
85+
fleetErrors: fleetError?.errors,
86+
fleetUuid: fleetError?.uuid,
87+
axiosMessage: error instanceof Error ? error.message : String(error),
88+
stack: error instanceof Error ? error.stack : undefined,
89+
url: error instanceof AxiosError ? error.config?.url : undefined,
90+
method: error instanceof AxiosError ? error.config?.method : undefined,
91+
fullResponseData: fleetError,
92+
});
93+
throw error;
94+
}
95+
}
96+
97+
// Store label ID in organization (only if we got a successful response)
98+
if (labelResponse && labelResponse.data?.label?.id) {
99+
await db.organization.update({
100+
where: {
101+
id: organizationId,
102+
},
103+
data: {
104+
fleetDmLabelId: labelResponse.data.label.id,
105+
},
106+
});
107+
108+
logger.info('Stored Fleet label ID in organization', {
109+
organizationId,
110+
labelId: labelResponse.data.label.id,
111+
});
112+
} else {
113+
logger.info('Skipping Fleet label ID storage - label already exists or creation failed', {
114+
organizationId,
115+
});
116+
}
68117

69118
try {
70119
await db.organization.update({
@@ -73,7 +122,7 @@ export const createFleetLabelForOrg = task({
73122
isFleetSetupCompleted: true,
74123
},
75124
});
76-
logger.info(`Stored S3 bundle URL for organization ${organizationId}`);
125+
logger.info(`Updated organization ${organizationId} to mark fleet setup as completed`);
77126
} catch (error) {
78127
logger.error('Error in fleetctl packaging or S3 upload process', {
79128
error,

apps/portal/src/app/api/download-agent/fleet-label.ts

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,61 @@ export async function createFleetLabel({
1111
fleetDevicePathMac,
1212
fleetDevicePathWindows,
1313
}: CreateFleetLabelParams): Promise<void> {
14+
logger('createFleetLabel function called', {
15+
employeeId,
16+
memberId,
17+
os,
18+
fleetDevicePathMac,
19+
fleetDevicePathWindows,
20+
});
21+
1422
try {
23+
logger('Getting Fleet instance...');
1524
const fleet = await getFleetInstance();
25+
logger('Fleet instance obtained successfully');
1626

1727
// Create platform-specific query
1828
const query =
1929
os === 'macos'
2030
? `SELECT 1 FROM file WHERE path = '${fleetDevicePathMac}/${employeeId}' LIMIT 1;`
2131
: `SELECT 1 FROM file WHERE path = '${fleetDevicePathWindows}\\${employeeId}' LIMIT 1;`;
2232

33+
logger('Generated Fleet query for label creation', {
34+
employeeId,
35+
os,
36+
query,
37+
});
38+
39+
logger('Sending POST request to Fleet API to create label...', {
40+
labelName: employeeId,
41+
endpoint: '/labels',
42+
requestBody: {
43+
name: employeeId,
44+
query: query,
45+
},
46+
});
47+
2348
const response = await fleet.post('/labels', {
2449
name: employeeId,
2550
query: query,
2651
});
2752

53+
logger('Fleet API response received', {
54+
status: response.status,
55+
statusText: response.statusText,
56+
labelId: response.data?.label?.id,
57+
responseData: response.data,
58+
headers: response.headers,
59+
});
60+
2861
const labelId = response.data.label.id;
2962

63+
logger('Updating member record with Fleet label ID', {
64+
memberId,
65+
labelId,
66+
employeeId,
67+
});
68+
3069
await db.member.update({
3170
where: {
3271
id: memberId,
@@ -35,11 +74,47 @@ export async function createFleetLabel({
3574
fleetDmLabelId: labelId,
3675
},
3776
});
77+
78+
logger('Member record updated successfully with Fleet label ID', {
79+
memberId,
80+
labelId,
81+
employeeId,
82+
});
3883
} catch (error) {
3984
if (error instanceof AxiosError && error.response?.status === 409) {
4085
// Label already exists, which is fine.
41-
logger('Fleet label already exists, skipping creation.', { employeeId });
86+
const fleetError = error.response.data;
87+
logger('Fleet label already exists, skipping creation.', {
88+
employeeId,
89+
httpStatus: error.response.status,
90+
httpStatusText: error.response.statusText,
91+
fleetMessage: fleetError?.message,
92+
fleetErrors: fleetError?.errors,
93+
fleetUuid: fleetError?.uuid,
94+
axiosMessage: error.message,
95+
url: error.config?.url,
96+
method: error.config?.method,
97+
fullResponseData: error.response.data,
98+
});
4299
} else {
100+
// Log the error details before re-throwing
101+
const fleetError = error instanceof AxiosError ? error.response?.data : null;
102+
logger('Error creating Fleet label', {
103+
employeeId,
104+
memberId,
105+
os,
106+
httpStatus: error instanceof AxiosError ? error.response?.status : undefined,
107+
httpStatusText: error instanceof AxiosError ? error.response?.statusText : undefined,
108+
fleetMessage: fleetError?.message,
109+
fleetErrors: fleetError?.errors,
110+
fleetUuid: fleetError?.uuid,
111+
axiosMessage: error instanceof Error ? error.message : String(error),
112+
stack: error instanceof Error ? error.stack : undefined,
113+
url: error instanceof AxiosError ? error.config?.url : undefined,
114+
method: error instanceof AxiosError ? error.config?.method : undefined,
115+
fullResponseData: fleetError,
116+
});
117+
43118
// Re-throw other errors
44119
throw error;
45120
}
Lines changed: 0 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
1-
import { auth } from '@/app/lib/auth';
21
import { logger } from '@/utils/logger';
32
import { s3Client } from '@/utils/s3';
43
import { GetObjectCommand } from '@aws-sdk/client-s3';
54
import { client as kv } from '@comp/kv';
65
import archiver from 'archiver';
76
import { type NextRequest, NextResponse } from 'next/server';
87
import { PassThrough, Readable } from 'stream';
9-
import { createFleetLabel } from './fleet-label';
108
import {
119
generateMacScript,
1210
generateWindowsScript,
1311
getPackageFilename,
1412
getReadmeContent,
1513
getScriptFilename,
1614
} from './scripts';
17-
import type { DownloadAgentRequest, SupportedOS } from './types';
18-
import { detectOSFromUserAgent, validateMemberAndOrg } from './utils';
1915

2016
// GET handler for direct browser downloads using token
2117
export async function GET(req: NextRequest) {
@@ -110,132 +106,3 @@ export async function GET(req: NextRequest) {
110106
return new NextResponse('Failed to create download', { status: 500 });
111107
}
112108
}
113-
114-
// POST handler remains the same for backward compatibility or direct API usage
115-
export async function POST(req: NextRequest) {
116-
// Authentication
117-
const session = await auth.api.getSession({
118-
headers: req.headers,
119-
});
120-
121-
if (!session?.user) {
122-
return new NextResponse('Unauthorized', { status: 401 });
123-
}
124-
125-
// Validate request body
126-
const { orgId, employeeId }: DownloadAgentRequest = await req.json();
127-
128-
if (!orgId || !employeeId) {
129-
return new NextResponse('Missing orgId or employeeId', { status: 400 });
130-
}
131-
132-
// Auto-detect OS from User-Agent
133-
const userAgent = req.headers.get('user-agent');
134-
const detectedOS = detectOSFromUserAgent(userAgent);
135-
136-
if (!detectedOS) {
137-
return new NextResponse(
138-
'Could not detect OS from User-Agent. Please use a standard browser on macOS or Windows.',
139-
{ status: 400 },
140-
);
141-
}
142-
143-
const os = detectedOS;
144-
logger('Auto-detected OS from User-Agent', { os, userAgent });
145-
146-
// Check environment configuration
147-
const fleetDevicePathMac = process.env.FLEET_DEVICE_PATH_MAC;
148-
const fleetDevicePathWindows = process.env.FLEET_DEVICE_PATH_WINDOWS;
149-
const fleetBucketName = process.env.FLEET_AGENT_BUCKET_NAME;
150-
151-
if (!fleetDevicePathMac || !fleetDevicePathWindows) {
152-
logger(
153-
'FLEET_DEVICE_PATH_MAC or FLEET_DEVICE_PATH_WINDOWS not configured in environment variables',
154-
);
155-
return new NextResponse(
156-
'Server configuration error: FLEET_DEVICE_PATH_MAC or FLEET_DEVICE_PATH_WINDOWS is missing.',
157-
{
158-
status: 500,
159-
},
160-
);
161-
}
162-
163-
if (!fleetBucketName) {
164-
return new NextResponse('Server configuration error: Fleet bucket name is missing.', {
165-
status: 500,
166-
});
167-
}
168-
169-
// Validate member and organization
170-
const member = await validateMemberAndOrg(session.user.id, orgId);
171-
if (!member) {
172-
return new NextResponse('Member not found or organization invalid', { status: 404 });
173-
}
174-
175-
// Generate OS-specific script
176-
const fleetDevicePath = os === 'macos' ? fleetDevicePathMac : fleetDevicePathWindows;
177-
const script =
178-
os === 'macos'
179-
? generateMacScript({ orgId, employeeId, fleetDevicePath })
180-
: generateWindowsScript({ orgId, employeeId, fleetDevicePath });
181-
182-
try {
183-
// Create Fleet label
184-
await createFleetLabel({
185-
employeeId,
186-
memberId: member.id,
187-
os: os as SupportedOS,
188-
fleetDevicePathMac,
189-
fleetDevicePathWindows,
190-
});
191-
192-
// Create a passthrough stream for the response
193-
const passThrough = new PassThrough();
194-
const archive = archiver('zip', { zlib: { level: 9 } });
195-
196-
// Pipe archive to passthrough
197-
archive.pipe(passThrough);
198-
199-
// Add script file
200-
const scriptFilename = getScriptFilename(os);
201-
archive.append(script, { name: scriptFilename, mode: 0o755 });
202-
203-
// Add README
204-
const readmeContent = getReadmeContent(os);
205-
archive.append(readmeContent, { name: 'README.txt' });
206-
207-
// Get package from S3 and stream it
208-
const packageFilename = getPackageFilename(os);
209-
const packageKey = `${os}/fleet-osquery.${os === 'macos' ? 'pkg' : 'msi'}`;
210-
211-
const getObjectCommand = new GetObjectCommand({
212-
Bucket: fleetBucketName,
213-
Key: packageKey,
214-
});
215-
216-
const s3Response = await s3Client.send(getObjectCommand);
217-
218-
if (s3Response.Body) {
219-
const s3Stream = s3Response.Body as Readable;
220-
archive.append(s3Stream, { name: packageFilename, store: true });
221-
}
222-
223-
// Finalize the archive
224-
archive.finalize();
225-
226-
// Convert Node.js stream to Web Stream for NextResponse
227-
const webStream = Readable.toWeb(passThrough) as unknown as ReadableStream;
228-
229-
// Return streaming response
230-
return new NextResponse(webStream, {
231-
headers: {
232-
'Content-Type': 'application/zip',
233-
'Content-Disposition': `attachment; filename="compai-device-agent-${os}.zip"`,
234-
'Cache-Control': 'no-cache',
235-
},
236-
});
237-
} catch (error) {
238-
logger('Error creating agent download', { error });
239-
return new NextResponse('Failed to create download', { status: 500 });
240-
}
241-
}

0 commit comments

Comments
 (0)