diff --git a/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/Employee.tsx b/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/Employee.tsx
index ed9631250..8e786a0b0 100644
--- a/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/Employee.tsx
+++ b/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/Employee.tsx
@@ -16,7 +16,6 @@ interface EmployeeDetailsProps {
})[];
fleetPolicies: FleetPolicy[];
host: Host;
- isFleetEnabled: boolean;
}
export function Employee({
@@ -25,7 +24,6 @@ export function Employee({
trainingVideos,
fleetPolicies,
host,
- isFleetEnabled,
}: EmployeeDetailsProps) {
return (
@@ -36,7 +34,6 @@ export function Employee({
trainingVideos={trainingVideos}
fleetPolicies={fleetPolicies}
host={host}
- isFleetEnabled={isFleetEnabled}
/>
);
diff --git a/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/EmployeeTasks.tsx b/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/EmployeeTasks.tsx
index 35a938e3f..4a0ecb92b 100644
--- a/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/EmployeeTasks.tsx
+++ b/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/EmployeeTasks.tsx
@@ -13,7 +13,6 @@ export const EmployeeTasks = ({
trainingVideos,
host,
fleetPolicies,
- isFleetEnabled,
}: {
employee: Member & {
user: User;
@@ -24,7 +23,6 @@ export const EmployeeTasks = ({
})[];
host: Host;
fleetPolicies: FleetPolicy[];
- isFleetEnabled: boolean;
}) => {
return (
diff --git a/apps/app/src/jobs/tasks/device/create-fleet-label-for-org.ts b/apps/app/src/jobs/tasks/device/create-fleet-label-for-org.ts
index deb595fae..73548ef27 100644
--- a/apps/app/src/jobs/tasks/device/create-fleet-label-for-org.ts
+++ b/apps/app/src/jobs/tasks/device/create-fleet-label-for-org.ts
@@ -29,20 +29,21 @@ export const createFleetLabelForOrg = task({
return;
}
- const fleetDevicePathMac = process.env.FLEET_DEVICE_PATH_MAC;
- const fleetDevicePathWindows = process.env.FLEET_DEVICE_PATH_WINDOWS;
+ const fleetDevicePathMac = '/Users/Shared/.fleet';
+ const fleetDevicePathWindows = 'C:\\ProgramData\\CompAI\\Fleet';
+ const windowsFallbackDir = 'C:\\Users\\Public\\CompAI\\Fleet';
- if (!fleetDevicePathMac || !fleetDevicePathWindows) {
- logger.error('FLEET_DEVICE_PATH_MAC or FLEET_DEVICE_PATH_WINDOWS not configured');
- return;
- }
-
- // Create a query that matches devices from either macOS or Windows
- const query = `SELECT 1 FROM file WHERE path = '${fleetDevicePathMac}/${organizationId}' OR path = '${fleetDevicePathWindows}\\${organizationId}' LIMIT 1;`;
+ // Simple union query: only file table, constrained per platform path
+ const query = `
+ SELECT 1 FROM file WHERE path = '${fleetDevicePathMac}/${organizationId}'
+ UNION SELECT 1 FROM file WHERE path = '${fleetDevicePathWindows}\\${organizationId}'
+ UNION SELECT 1 FROM file WHERE path = '${windowsFallbackDir}\\${organizationId}'
+ LIMIT 1;`;
+ const normalizedQuery = query.replace(/\s+/g, ' ').trim();
logger.info('Creating label', {
name: organization.id,
- query: query,
+ query: normalizedQuery,
});
const fleet = await getFleetInstance();
@@ -53,7 +54,7 @@ export const createFleetLabelForOrg = task({
// Create a manual label that we can assign to hosts.
labelResponse = await fleet.post('/labels', {
name: organization.id,
- query: query,
+ query: normalizedQuery,
});
logger.info('Label created', {
diff --git a/apps/portal/src/app/(app)/(home)/[orgId]/components/EmployeeTasksList.tsx b/apps/portal/src/app/(app)/(home)/[orgId]/components/EmployeeTasksList.tsx
index 69e5292da..4113cdcc2 100644
--- a/apps/portal/src/app/(app)/(home)/[orgId]/components/EmployeeTasksList.tsx
+++ b/apps/portal/src/app/(app)/(home)/[orgId]/components/EmployeeTasksList.tsx
@@ -15,7 +15,6 @@ interface EmployeeTasksListProps {
member: Member;
fleetPolicies: FleetPolicy[];
host: Host | null;
- isFleetEnabled: boolean;
}
export const EmployeeTasksList = ({
@@ -24,7 +23,6 @@ export const EmployeeTasksList = ({
member,
fleetPolicies,
host,
- isFleetEnabled,
}: EmployeeTasksListProps) => {
// Check completion status
const hasAcceptedPolicies =
@@ -58,16 +56,12 @@ export const EmployeeTasksList = ({
title: 'Accept security policies',
content:
,
},
- ...(isFleetEnabled
- ? [
- {
- title: 'Download and install Comp AI Device Agent',
- content: (
-
- ),
- },
- ]
- : []),
+ {
+ title: 'Download and install Comp AI Device Agent',
+ content: (
+
+ ),
+ },
{
title: 'Complete general security awareness training',
content:
,
diff --git a/apps/portal/src/app/(app)/(home)/[orgId]/components/OrganizationDashboard.tsx b/apps/portal/src/app/(app)/(home)/[orgId]/components/OrganizationDashboard.tsx
index a8a56080e..48feb21e8 100644
--- a/apps/portal/src/app/(app)/(home)/[orgId]/components/OrganizationDashboard.tsx
+++ b/apps/portal/src/app/(app)/(home)/[orgId]/components/OrganizationDashboard.tsx
@@ -15,7 +15,6 @@ interface OrganizationDashboardProps {
member: MemberWithUserOrg; // Pass the full member object for user info etc.
fleetPolicies: FleetPolicy[];
host: Host | null;
- isFleetEnabled: boolean;
}
export async function OrganizationDashboard({
@@ -23,7 +22,6 @@ export async function OrganizationDashboard({
member,
fleetPolicies,
host,
- isFleetEnabled,
}: OrganizationDashboardProps) {
// Fetch policies specific to the selected organization
const policies = await db.policy.findMany({
@@ -74,7 +72,6 @@ export async function OrganizationDashboard({
member={member} // Pass the member object down
fleetPolicies={fleetPolicies}
host={host}
- isFleetEnabled={isFleetEnabled ?? false}
/>
);
}
diff --git a/apps/portal/src/app/(app)/(home)/[orgId]/page.tsx b/apps/portal/src/app/(app)/(home)/[orgId]/page.tsx
index 91a7483d7..79b16971c 100644
--- a/apps/portal/src/app/(app)/(home)/[orgId]/page.tsx
+++ b/apps/portal/src/app/(app)/(home)/[orgId]/page.tsx
@@ -1,7 +1,6 @@
'use server';
import { auth } from '@/app/lib/auth';
-import { getPostHogClient } from '@/app/posthog';
import { getFleetInstance } from '@/utils/fleet';
import type { Member } from '@db';
import { db } from '@db';
@@ -45,26 +44,13 @@ export default async function OrganizationPage({ params }: { params: Promise<{ o
return redirect('/'); // Or appropriate login/auth route
}
- // Check fleet feature flag first
- let isFleetEnabled = false;
- try {
- const postHogClient = await getPostHogClient();
- isFleetEnabled =
- (await postHogClient?.isFeatureEnabled('is-fleet-enabled', session?.user.id)) ?? false;
- } catch (error) {
- console.error('Error checking fleet feature flag:', error);
- // Default to false if there's an error
- }
-
// Only fetch fleet policies if fleet is enabled
let fleetPolicies: FleetPolicy[] = [];
let device: Host | null = null;
- if (isFleetEnabled) {
- const fleetData = await getFleetPolicies(member);
- fleetPolicies = fleetData.fleetPolicies;
- device = fleetData.device;
- }
+ const fleetData = await getFleetPolicies(member);
+ fleetPolicies = fleetData.fleetPolicies;
+ device = fleetData.device;
return (
);
} catch (error) {
diff --git a/apps/portal/src/app/api/download-agent/fleet-label.ts b/apps/portal/src/app/api/download-agent/fleet-label.ts
index 2ebaa546e..259143e31 100644
--- a/apps/portal/src/app/api/download-agent/fleet-label.ts
+++ b/apps/portal/src/app/api/download-agent/fleet-label.ts
@@ -24,16 +24,21 @@ export async function createFleetLabel({
const fleet = await getFleetInstance();
logger('Fleet instance obtained successfully');
- // Create platform-specific query
+ // OS-specific queries: mac uses file-only; Windows uses UNION with file and registry
const query =
os === 'macos'
? `SELECT 1 FROM file WHERE path = '${fleetDevicePathMac}/${employeeId}' LIMIT 1;`
- : `SELECT 1 FROM file WHERE path = '${fleetDevicePathWindows}\\${employeeId}' LIMIT 1;`;
+ : `SELECT 1 FROM file WHERE path = '${fleetDevicePathWindows}\\${employeeId}'
+ UNION SELECT 1 FROM file WHERE path = 'C:\\Users\\Public\\CompAI\\Fleet\\${employeeId}'
+ LIMIT 1;`;
+
+ // Normalize whitespace to a single line to avoid issues with newlines/tabs
+ const normalizedQuery = query.replace(/\s+/g, ' ').trim();
logger('Generated Fleet query for label creation', {
employeeId,
os,
- query,
+ query: normalizedQuery,
});
logger('Sending POST request to Fleet API to create label...', {
@@ -41,13 +46,13 @@ export async function createFleetLabel({
endpoint: '/labels',
requestBody: {
name: employeeId,
- query: query,
+ query: normalizedQuery,
},
});
const response = await fleet.post('/labels', {
name: employeeId,
- query: query,
+ query: normalizedQuery,
});
logger('Fleet API response received', {
diff --git a/apps/portal/src/app/api/download-agent/route.ts b/apps/portal/src/app/api/download-agent/route.ts
index 40e085e11..ccfafac24 100644
--- a/apps/portal/src/app/api/download-agent/route.ts
+++ b/apps/portal/src/app/api/download-agent/route.ts
@@ -13,6 +13,10 @@ import {
getScriptFilename,
} from './scripts';
+export const runtime = 'nodejs';
+export const dynamic = 'force-dynamic';
+export const maxDuration = 60;
+
// GET handler for direct browser downloads using token
export async function GET(req: NextRequest) {
const searchParams = req.nextUrl.searchParams;
@@ -39,12 +43,12 @@ export async function GET(req: NextRequest) {
os: 'macos' | 'windows';
};
- // Check environment configuration
- const fleetDevicePathMac = process.env.FLEET_DEVICE_PATH_MAC;
- const fleetDevicePathWindows = process.env.FLEET_DEVICE_PATH_WINDOWS;
+ // Hardcoded device marker paths used by the setup scripts
+ const fleetDevicePathMac = '/Users/Shared/.fleet';
+ const fleetDevicePathWindows = 'C:\\ProgramData\\CompAI\\Fleet';
const fleetBucketName = process.env.FLEET_AGENT_BUCKET_NAME;
- if (!fleetDevicePathMac || !fleetDevicePathWindows || !fleetBucketName) {
+ if (!fleetBucketName) {
return new NextResponse('Server configuration error', { status: 500 });
}
@@ -63,6 +67,21 @@ export async function GET(req: NextRequest) {
// Pipe archive to passthrough
archive.pipe(passThrough);
+ // Robust error handling for staging/prod reliability
+ archive.on('error', (err) => {
+ logger('archiver_error', { message: err?.message, stack: (err as Error)?.stack });
+ passThrough.destroy(err as Error);
+ });
+ archive.on('warning', (warn) => {
+ logger('archiver_warning', { message: (warn as Error)?.message });
+ });
+ passThrough.on('error', (err) => {
+ logger('download_stream_error', {
+ message: (err as Error)?.message,
+ stack: (err as Error)?.stack,
+ });
+ });
+
// Add script file
const scriptFilename = getScriptFilename(os);
archive.append(script, { name: scriptFilename, mode: 0o755 });
@@ -84,6 +103,13 @@ export async function GET(req: NextRequest) {
if (s3Response.Body) {
const s3Stream = s3Response.Body as Readable;
+ s3Stream.on('error', (err) => {
+ logger('s3_stream_error', {
+ message: (err as Error)?.message,
+ stack: (err as Error)?.stack,
+ });
+ passThrough.destroy(err as Error);
+ });
archive.append(s3Stream, { name: packageFilename, store: true });
}
@@ -99,6 +125,7 @@ export async function GET(req: NextRequest) {
'Content-Type': 'application/zip',
'Content-Disposition': `attachment; filename="compai-device-agent-${os}.zip"`,
'Cache-Control': 'no-cache, no-store, must-revalidate',
+ 'X-Accel-Buffering': 'no',
},
});
} catch (error) {
diff --git a/apps/portal/src/app/api/download-agent/scripts.ts b/apps/portal/src/app/api/download-agent/scripts.ts
index 11b25c9de..11e3d7c6b 100644
--- a/apps/portal/src/app/api/download-agent/scripts.ts
+++ b/apps/portal/src/app/api/download-agent/scripts.ts
@@ -1,73 +1,11 @@
-import type { ScriptConfig, SupportedOS } from './types';
-
-export function generateMacScript(config: ScriptConfig): string {
- const { orgId, employeeId, fleetDevicePath } = config;
-
- return `#!/bin/bash
-# Create org marker for Fleet policies/labels
-set -euo pipefail
-ORG_ID="${orgId}"
-EMPLOYEE_ID="${employeeId}"
-FLEET_DIR="${fleetDevicePath}"
-mkdir -p "$FLEET_DIR"
-echo "$ORG_ID" > "$FLEET_DIR/${orgId}"
-echo "$EMPLOYEE_ID" > "$FLEET_DIR/${employeeId}"
-chmod 755 "$FLEET_DIR"
-chmod 644 "$FLEET_DIR/${orgId}"
-chmod 644 "$FLEET_DIR/${employeeId}"
-exit 0`;
-}
-
-export function generateWindowsScript(config: ScriptConfig): string {
- const { orgId, employeeId, fleetDevicePath } = config;
-
- return `@echo off
-REM Create org marker for Fleet policies/labels
-setlocal
-set ORG_ID=${orgId}
-set EMPLOYEE_ID=${employeeId}
-set FLEET_DIR=${fleetDevicePath}
-if not exist "%FLEET_DIR%" mkdir "%FLEET_DIR%"
-echo %ORG_ID% > "%FLEET_DIR%\\%ORG_ID%"
-echo %EMPLOYEE_ID% > "%FLEET_DIR%\\%EMPLOYEE_ID%"
-exit /b 0`;
-}
-
-export function getScriptFilename(os: SupportedOS): string {
- return os === 'macos' ? 'run_me_first.command' : 'run_me_first.bat';
-}
-
-export function getPackageFilename(os: SupportedOS): string {
- return os === 'macos' ? 'compai-device-agent.pkg' : 'compai-device-agent.msi';
-}
-
-export function getReadmeContent(os: SupportedOS): string {
- if (os === 'macos') {
- return `Installation Instructions for macOS:
-
-1. First, run the setup script by double-clicking "run_me_first.command"
- - This will create the necessary organization markers for device management
- - You may need to allow the script to run in System Preferences > Security & Privacy
-
-2. Then, install the agent by double-clicking "compai-device-agent.pkg"
- - Follow the installation wizard
- - You may need to allow the installer in System Preferences > Security & Privacy
-
-3. The agent will start automatically after installation
-`;
- }
-
- return `Installation Instructions for Windows:
-
-1. First, run the setup script:
- - Right-click on "run_me_first.bat" and select "Run as administrator"
- - This will create the necessary organization markers for device management
-
-2. Then, install the agent:
- - Double-click "compai-device-agent.msi"
- - Follow the installation wizard
- - Windows may show a security warning - click "More info" and then "Run anyway"
-
-3. The agent will start automatically after installation
-`;
-}
+import { getPackageFilename, getReadmeContent, getScriptFilename } from './scripts/common';
+import { generateMacScript } from './scripts/mac';
+import { generateWindowsScript } from './scripts/windows';
+
+export {
+ generateMacScript,
+ generateWindowsScript,
+ getPackageFilename,
+ getReadmeContent,
+ getScriptFilename,
+};
diff --git a/apps/portal/src/app/api/download-agent/scripts/common.ts b/apps/portal/src/app/api/download-agent/scripts/common.ts
new file mode 100644
index 000000000..dbe01eeac
--- /dev/null
+++ b/apps/portal/src/app/api/download-agent/scripts/common.ts
@@ -0,0 +1,44 @@
+import type { SupportedOS } from '../types';
+
+export function getScriptFilename(os: SupportedOS): string {
+ return os === 'macos' ? 'run_me_first.command' : 'run_me_first.bat';
+}
+
+export function getPackageFilename(os: SupportedOS): string {
+ return os === 'macos' ? 'compai-device-agent.pkg' : 'compai-device-agent.msi';
+}
+
+export function getReadmeContent(os: SupportedOS): string {
+ if (os === 'macos') {
+ return `Installation Instructions for macOS:
+
+1. First, run the setup script by double-clicking "run_me_first.command"
+ - This will create the necessary organization markers for device management
+ - You may need to allow the script to run in System Preferences > Security & Privacy
+
+2. Then, install the agent by double-clicking "compai-device-agent.pkg"
+ - Follow the installation wizard
+ - You may need to allow the installer in System Preferences > Security & Privacy
+
+3. The agent will start automatically after installation
+`;
+ }
+
+ return `Installation Instructions for Windows:
+
+1. First, run the setup script:
+ - Right-click on "run_me_first.bat" and select "Run as administrator" (required)
+ - This writes organization markers to the device and registry
+ - If prompted by SmartScreen, click "More info" -> "Run anyway"
+
+2. Then, install the agent:
+ - Double-click "compai-device-agent.msi" and follow the wizard
+
+3. Troubleshooting:
+ - If setup fails, open the log at: %ProgramData%\\CompAI\\Fleet or %Public%\\CompAI\\Fleet -> setup.log
+ - Ensure your antivirus or endpoint protection allows running local .bat files
+ - If you cannot run as administrator, ask IT to assist or install both files and registry keys manually
+
+4. After installation, the agent will start automatically.
+`;
+}
diff --git a/apps/portal/src/app/api/download-agent/scripts/index.ts b/apps/portal/src/app/api/download-agent/scripts/index.ts
new file mode 100644
index 000000000..613fb7274
--- /dev/null
+++ b/apps/portal/src/app/api/download-agent/scripts/index.ts
@@ -0,0 +1,3 @@
+export { getPackageFilename, getReadmeContent, getScriptFilename } from './common';
+export { generateMacScript } from './mac';
+export { generateWindowsScript } from './windows';
diff --git a/apps/portal/src/app/api/download-agent/scripts/mac.ts b/apps/portal/src/app/api/download-agent/scripts/mac.ts
new file mode 100644
index 000000000..d51c854f2
--- /dev/null
+++ b/apps/portal/src/app/api/download-agent/scripts/mac.ts
@@ -0,0 +1,120 @@
+import type { ScriptConfig } from '../types';
+
+export function generateMacScript(config: ScriptConfig): string {
+ const { orgId, employeeId, fleetDevicePath } = config;
+
+ return `#!/bin/bash
+# CompAI Device Setup (macOS)
+# Creates organization markers for Fleet policies/labels with clear, human-readable output
+
+set -uo pipefail
+
+ORG_ID="${orgId}"
+EMPLOYEE_ID="${employeeId}"
+FLEET_DIR="${fleetDevicePath}"
+LOG_FILE="/tmp/compai-setup.log"
+HAS_ERROR=0
+ERROR_TEXT=""
+
+# Colors (ANSI escapes)
+NC='\x1b[0m'
+GREEN='\x1b[0;32m'
+YELLOW='\x1b[0;33m'
+RED='\x1b[0;31m'
+BOLD='\x1b[1m'
+
+timestamp() { date '+%Y-%m-%d %H:%M:%S'; }
+log_info() { printf "[%s] %bINFO%b %s\n" "$(timestamp)" "$GREEN" "$NC" "$1" | tee -a "$LOG_FILE"; }
+log_warn() { printf "[%s] %bWARN%b %s\n" "$(timestamp)" "$YELLOW" "$NC" "$1" | tee -a "$LOG_FILE"; }
+log_error() { printf "[%s] %bERROR%b %s\n" "$(timestamp)" "$RED" "$NC" "$1" | tee -a "$LOG_FILE"; HAS_ERROR=1; ERROR_TEXT+=" - $1"$'\n'; }
+
+echo "------------------------------------------------------------"
+printf "%b\n" "$BOLD CompAI Device Setup (macOS)$NC"
+echo "Organization: $ORG_ID"
+echo "Employee: $EMPLOYEE_ID"
+echo "Date: $(timestamp)"
+echo "Log file: $LOG_FILE"
+echo "------------------------------------------------------------"
+echo
+
+# Determine if we need sudo for the fleet directory
+SUDO=""
+if [ ! -d "$FLEET_DIR" ]; then
+ log_info "Creating directory: $FLEET_DIR"
+ if mkdir -p "$FLEET_DIR" 2>>"$LOG_FILE"; then
+ :
+ else
+ log_warn "No write access creating $FLEET_DIR; retrying with sudo (you may be prompted for your password)."
+ if sudo mkdir -p "$FLEET_DIR" 2>>"$LOG_FILE"; then
+ SUDO="sudo"
+ else
+ log_error "Failed to create directory $FLEET_DIR even with sudo."
+ fi
+ fi
+fi
+
+if [ -d "$FLEET_DIR" ] && [ ! -w "$FLEET_DIR" ]; then
+ SUDO="sudo"
+fi
+
+if [ -z "$SUDO" ]; then
+ log_info "Using directory: $FLEET_DIR (no sudo needed)"
+else
+ log_info "Using directory: $FLEET_DIR (sudo required)"
+fi
+
+# Write marker files
+if [ -d "$FLEET_DIR" ]; then
+ log_info "Writing organization marker file..."
+ if printf "%s" "$ORG_ID" | $SUDO tee "$FLEET_DIR/$ORG_ID" >/dev/null 2>>"$LOG_FILE"; then
+ log_info "[OK] Organization marker: $FLEET_DIR/$ORG_ID"
+ else
+ log_error "Failed writing organization marker to $FLEET_DIR/$ORG_ID"
+ fi
+
+ log_info "Writing employee marker file..."
+ if printf "%s" "$EMPLOYEE_ID" | $SUDO tee "$FLEET_DIR/$EMPLOYEE_ID" >/dev/null 2>>"$LOG_FILE"; then
+ log_info "[OK] Employee marker: $FLEET_DIR/$EMPLOYEE_ID"
+ else
+ log_error "Failed writing employee marker to $FLEET_DIR/$EMPLOYEE_ID"
+ fi
+
+ # Permissions
+ $SUDO chmod 755 "$FLEET_DIR" 2>>"$LOG_FILE" || log_warn "Could not chmod 755 on $FLEET_DIR"
+ $SUDO chmod 644 "$FLEET_DIR/$ORG_ID" 2>>"$LOG_FILE" || log_warn "Could not chmod 644 on $FLEET_DIR/$ORG_ID"
+ $SUDO chmod 644 "$FLEET_DIR/$EMPLOYEE_ID" 2>>"$LOG_FILE" || log_warn "Could not chmod 644 on $FLEET_DIR/$EMPLOYEE_ID"
+else
+ log_error "Directory not available: $FLEET_DIR"
+fi
+
+# Verify markers
+echo
+log_info "Verifying markers..."
+if [ -f "$FLEET_DIR/$EMPLOYEE_ID" ]; then
+ log_info "[OK] Employee marker file present."
+else
+ log_error "Employee marker file missing at $FLEET_DIR/$EMPLOYEE_ID"
+fi
+
+# Summary
+echo
+echo "------------------------------------------------------------"
+if [ "$HAS_ERROR" -eq 0 ]; then
+ printf "%b\n" "$GREEN RESULT: SUCCESS $NC"
+ echo "Setup completed successfully for $EMPLOYEE_ID."
+ echo "Files created in: $FLEET_DIR"
+else
+ printf "%b\n" "$RED RESULT: COMPLETED WITH ISSUES $NC"
+ echo "One or more steps did not complete successfully. Details:"
+ printf "%b" "$ERROR_TEXT"
+ echo
+ echo "Next steps:"
+ echo " - Take a screenshot of this window."
+ echo " - Attach the log file from: $LOG_FILE"
+ echo " - Share both with your CompAI support contact."
+fi
+echo "------------------------------------------------------------"
+echo
+read -r -p "Press Return to close this window..." _
+exit $HAS_ERROR`;
+}
diff --git a/apps/portal/src/app/api/download-agent/scripts/windows.ts b/apps/portal/src/app/api/download-agent/scripts/windows.ts
new file mode 100644
index 000000000..62183d425
--- /dev/null
+++ b/apps/portal/src/app/api/download-agent/scripts/windows.ts
@@ -0,0 +1,222 @@
+import type { ScriptConfig } from '../types';
+
+export function generateWindowsScript(config: ScriptConfig): string {
+ const { orgId, employeeId, fleetDevicePath } = config;
+
+ const script = `@echo off
+title CompAI Device Setup
+setlocal EnableExtensions EnableDelayedExpansion
+color 0A
+
+REM =========================
+REM Variables
+REM =========================
+set "ORG_ID=${orgId}"
+set "EMPLOYEE_ID=${employeeId}"
+set "PRIMARY_DIR=${fleetDevicePath}"
+set "FALLBACK_DIR=C:\\Users\\Public\\CompAI\\Fleet"
+set "CHOSEN_DIR="
+set "LOG_FILE="
+set "HAS_ERROR=0"
+set "ERRORS="
+set "EXIT_CODE=0"
+REM newline token (exactly this 2-line shape)
+set "nl=^
+"
+
+REM --- bootstrap log (updated once CHOSEN_DIR is known) ---
+set "LOG_FILE=%~dp0setup.log"
+
+goto :main
+
+REM =======================================================
+REM Subroutines (placed AFTER main to avoid early execution)
+REM =======================================================
+:log_msg
+setlocal EnableDelayedExpansion
+set "msg=%~1"
+echo [%date% %time%] !msg!
+>>"%LOG_FILE%" echo [%date% %time%] !msg!
+endlocal & exit /b 0
+
+:log_run
+setlocal EnableDelayedExpansion
+set "cmdline=%*"
+echo [%date% %time%] CMD: !cmdline!
+>>"%LOG_FILE%" echo [%date% %time%] CMD: !cmdline!
+%*
+set "rc=!errorlevel!"
+if not "!rc!"=="0" (
+ echo [%date% %time%] ERR !rc!: !cmdline!
+ >>"%LOG_FILE%" echo [%date% %time%] ERR !rc!: !cmdline!
+)
+endlocal & set "LAST_RC=%rc%"
+exit /b %LAST_RC%
+
+REM =========================
+REM Main
+REM =========================
+:main
+call :log_msg "Script starting"
+
+REM Admin check
+whoami /groups | find "S-1-16-12288" >nul 2>&1
+if errorlevel 1 (
+ color 0E
+ echo This script must be run as Administrator.
+ echo Please right-click the file and select "Run as administrator".
+ echo.
+ echo Press any key to exit, then try again with Administrator privileges.
+ pause
+ exit /b 5
+)
+
+REM Relaunch persistent window
+if not "%PERSIST%"=="1" (
+ set "PERSIST=1"
+ call :log_msg "Re-launching in a persistent window"
+ start "CompAI Device Setup" cmd /k "%~f0 %*"
+ exit /b
+)
+
+call :log_msg "Running with administrator privileges"
+call :log_msg "Current directory: %cd%"
+call :log_msg "Script path: %~f0"
+call :log_msg "Switching working directory to script folder"
+cd /d "%~dp0"
+call :log_msg "New current directory: %cd%"
+echo.
+
+REM Choose writable directory
+call :log_msg "Choosing destination directory; primary=%PRIMARY_DIR% fallback=%FALLBACK_DIR%"
+if exist "%PRIMARY_DIR%\\*" set "CHOSEN_DIR=%PRIMARY_DIR%"
+if not defined CHOSEN_DIR call :log_run mkdir "%PRIMARY_DIR%"
+if not defined CHOSEN_DIR if exist "%PRIMARY_DIR%\\*" set "CHOSEN_DIR=%PRIMARY_DIR%"
+
+if not defined CHOSEN_DIR call :log_msg "Primary not available; trying fallback"
+if not defined CHOSEN_DIR if exist "%FALLBACK_DIR%\\*" set "CHOSEN_DIR=%FALLBACK_DIR%"
+if not defined CHOSEN_DIR call :log_run mkdir "%FALLBACK_DIR%"
+if not defined CHOSEN_DIR if exist "%FALLBACK_DIR%\\*" set "CHOSEN_DIR=%FALLBACK_DIR%"
+
+if not defined CHOSEN_DIR (
+ color 0E
+ call :log_msg "WARNING: No writable directory found"
+ echo Primary attempted: "%PRIMARY_DIR%"
+ echo Fallback attempted: "%FALLBACK_DIR%"
+ echo [%date% %time%] No writable directory found. Primary: %PRIMARY_DIR%, Fallback: %FALLBACK_DIR% >> "%~dp0setup.log"
+ set "LOG_FILE=%~dp0setup.log"
+ set "HAS_ERROR=1"
+ set "ERRORS=!ERRORS!- No writable directory found (Primary: %PRIMARY_DIR%, Fallback: %FALLBACK_DIR%).!nl!"
+ set "EXIT_CODE=1"
+) else (
+ set "MARKER_DIR=%CHOSEN_DIR%"
+ if not "!MARKER_DIR:~-1!"=="\\" set "MARKER_DIR=!MARKER_DIR!\\"
+
+ REM switch the log file to the chosen directory, carry over bootstrap logs
+ set "FINAL_LOG=!MARKER_DIR!setup.log"
+ if /i not "%LOG_FILE%"=="%FINAL_LOG%" (
+ call :log_msg "Switching log to !FINAL_LOG!"
+ if exist "%LOG_FILE%" type "%LOG_FILE%" >> "!FINAL_LOG!" & del "%LOG_FILE%"
+ set "LOG_FILE=!FINAL_LOG!"
+ )
+ call :log_msg "Using directory: !MARKER_DIR!"
+)
+echo Logs will be written to: !LOG_FILE!
+echo.
+
+REM Write marker files
+if defined CHOSEN_DIR (
+ call :log_msg "Writing organization marker file"
+ call :log_msg "Preparing to write org marker to !MARKER_DIR!!ORG_ID!"
+ call :log_run cmd /c "(echo %ORG_ID%) > \"!MARKER_DIR!!ORG_ID!\""
+ if errorlevel 1 (
+ color 0E
+ call :log_msg "WARNING: Failed writing organization marker file to !MARKER_DIR!"
+ echo [%date% %time%] Failed writing org marker file >> "%LOG_FILE%"
+ set "HAS_ERROR=1"
+ set "ERRORS=!ERRORS!- Failed writing organization marker file.!nl!"
+ set "EXIT_CODE=1"
+ ) else (
+ call :log_msg "[OK] Organization marker file: !MARKER_DIR!!ORG_ID!"
+ )
+
+ call :log_msg "Writing employee marker file"
+ call :log_msg "Preparing to write employee marker to !MARKER_DIR!!EMPLOYEE_ID!"
+ call :log_run cmd /c "(echo %EMPLOYEE_ID%) > \"!MARKER_DIR!!EMPLOYEE_ID!\""
+ if errorlevel 1 (
+ color 0E
+ call :log_msg "WARNING: Failed writing employee marker file to !MARKER_DIR!"
+ echo [%date% %time%] Failed writing employee marker file >> "%LOG_FILE%"
+ set "HAS_ERROR=1"
+ set "ERRORS=!ERRORS!- Failed writing employee marker file.!nl!"
+ set "EXIT_CODE=1"
+ ) else (
+ call :log_msg "[OK] Employee marker file: !MARKER_DIR!!EMPLOYEE_ID!"
+ )
+)
+
+REM Permissions
+if defined CHOSEN_DIR (
+ call :log_msg "Setting permissions on marker directory"
+ call :log_run icacls "!MARKER_DIR!" /inheritance:e
+
+ call :log_msg "Granting read to SYSTEM and Administrators on org marker"
+ call :log_run icacls "!MARKER_DIR!!ORG_ID!" /grant *S-1-5-18:R *S-1-5-32-544:R
+
+ call :log_msg "Granting read to SYSTEM and Administrators on employee marker"
+ call :log_run icacls "!MARKER_DIR!!EMPLOYEE_ID!" /grant *S-1-5-18:R *S-1-5-32-544:R
+)
+
+REM Verify
+echo.
+echo Verifying markers...
+if defined CHOSEN_DIR (
+ call :log_msg "Verifying marker exists: !MARKER_DIR!!EMPLOYEE_ID!"
+ if not exist "!MARKER_DIR!!EMPLOYEE_ID!" (
+ color 0E
+ call :log_msg "WARNING: Employee marker file missing at !MARKER_DIR!!EMPLOYEE_ID!"
+ echo [%date% %time%] Verification failed: employee marker file missing >> "!LOG_FILE!"
+ set "HAS_ERROR=1"
+ set "ERRORS=!ERRORS!- Employee marker file missing at !MARKER_DIR!!EMPLOYEE_ID!!.!nl!"
+ set "EXIT_CODE=2"
+ ) else (
+ call :log_msg "[OK] Employee marker file present: !MARKER_DIR!!EMPLOYEE_ID!"
+ )
+)
+rem Skipping registry checks per request
+
+REM Result / Exit
+echo.
+echo ------------------------------------------------------------
+if "%HAS_ERROR%"=="0" (
+ color 0A
+ echo RESULT: SUCCESS
+ echo Setup completed successfully for %EMPLOYEE_ID%.
+ if defined CHOSEN_DIR echo Files created in: !CHOSEN_DIR!
+ echo Log file: !LOG_FILE!
+ call :log_msg "RESULT: SUCCESS"
+) else (
+ color 0C
+ echo RESULT: COMPLETED WITH ISSUES
+ echo One or more steps did not complete successfully. Details:
+ echo.
+ echo !ERRORS!
+ echo.
+ echo Next steps:
+ echo - Take a screenshot of this window.
+ echo - Attach the log file from: !LOG_FILE!
+ echo - Share both with your CompAI support contact.
+ call :log_msg "RESULT: COMPLETED WITH ISSUES (exit=%EXIT_CODE%)"
+)
+echo ------------------------------------------------------------
+echo.
+echo Press any key to close this window. This will not affect installation.
+pause
+if "%HAS_ERROR%"=="0" (exit /b 0) else (exit /b %EXIT_CODE%)
+
+REM End of main
+goto :eof
+`;
+
+ return script.replace(/\n/g, '\r\n');
+}
diff --git a/apps/portal/src/app/api/download-agent/token/route.ts b/apps/portal/src/app/api/download-agent/token/route.ts
index fabaf8032..883f3e8d1 100644
--- a/apps/portal/src/app/api/download-agent/token/route.ts
+++ b/apps/portal/src/app/api/download-agent/token/route.ts
@@ -49,19 +49,9 @@ export async function POST(req: NextRequest) {
userId: session.user.id,
});
- // Check environment configuration for fleet label creation
- const fleetDevicePathMac = process.env.FLEET_DEVICE_PATH_MAC;
- const fleetDevicePathWindows = process.env.FLEET_DEVICE_PATH_WINDOWS;
-
- if (!fleetDevicePathMac || !fleetDevicePathWindows) {
- logger('Fleet device paths not configured in token route', {
- fleetDevicePathMac: !!fleetDevicePathMac,
- fleetDevicePathWindows: !!fleetDevicePathWindows,
- });
- return new NextResponse('Server configuration error: Fleet device paths are missing.', {
- status: 500,
- });
- }
+ // Hardcoded device marker paths used by the setup scripts
+ const fleetDevicePathMac = '/Users/Shared/.fleet';
+ const fleetDevicePathWindows = 'C:\\ProgramData\\CompAI\\Fleet';
// Create Fleet label
try {