---
module: technical.fleet
title: Fleet — fleet task orchestration & artifact delivery
tab: Overview
order: 10
audience: dev
---

> **DRAFT · internal.** Written 2026-07-01 from hiveops-fleet source. Verify against code before relying on specifics.

## Responsibility

`hiveops-fleet` owns fleet tasks (download/install/software/config/capture jobs targeting devices), the uploadable/CDN-imported artifacts they deliver, patch (maintenance) windows, and network capture jobs. Agents pull work and report status through internal (`X-Internal-Secret`) endpoints; agent-proxy is the caller. Fleet keeps its own local device snapshot (`device_summary`) synced from Kafka rather than calling devices/incident over HTTP. Task terminal history is archived locally and completion is published to Kafka for other services (devices stores the durable device record). Cross-link [platform.service-ownership].

## Data

- **Database:** `hiveiq_fleet` on PostgreSQL (Spring `spring.datasource.url`, overridable via env `DB_URL`; port 8097). `ddl-auto=validate`, Flyway-managed.
- **Key tables** (V1__init_fleet.sql):
  - `fleet_tasks` — active tasks (PENDING/QUEUED/RUNNING lifecycle)
  - `fleet_task_history` — archived terminal tasks (COMPLETED/FAILED)
  - `fleet_artifacts` — uploadable/CDN-imported artifacts (agents, patches, scripts); `cdn_url` added V7
  - `patch_windows` — scheduled maintenance windows (+ `patch_window_devices`, V4)
  - `atm_captures` — network capture jobs
  - `device_summary` — local device-state snapshot synced via Kafka (not authoritative)

Cross-link [platform.data-stores].

## Kafka

Cross-link [platform.kafka].

**Produced:**

| Topic | Producer class | Notes |
|-------|----------------|-------|
| `hiveops.fleet.task.events` | `FleetTaskEventPublisher` (via `FleetTaskService`) | Emits `created` / status-update / `archived` `FleetTaskEvent`s; topic constant `FleetTaskKafkaConfig.FLEET_TASK_EVENTS_TOPIC`, 3 partitions, idempotent acks=all producer. |

**Consumed:**

| Topic | Listener class | Notes |
|-------|----------------|-------|
| `hiveops.device.events` | `DeviceEventConsumer` (`@KafkaListener`, groupId `hiveops-fleet-device-sync`) | Upserts `device_summary`; trusts packages `com.hiveops.devices.kafka`, `com.hiveops.fleet.kafka`; `auto-offset-reset=earliest`. |

## API surface

Base paths: `/api/fleet` (authenticated), `/api/fleet/patch-windows`, `/api/internal/fleet` (agent-proxy only, `hasRole('INTERNAL')`), `/api/users`.

**`FleetApiController` (`/api/fleet`)** — selected:

| Method Path | Purpose |
|-------------|---------|
| GET `/api/fleet/tasks` (+ `/tasks/paginated`) | List fleet tasks |
| POST `/api/fleet/tasks` | Create a fleet task |
| POST `/api/fleet/tasks/{id}/cancel` | Cancel a task |
| POST `/api/fleet/tasks/{id}/retry` | Retry a task |
| GET `/api/fleet/tasks/pending-approval` | Tasks awaiting approver action |
| POST `/api/fleet/tasks/{id}/approve` · `/reject` | Approver decision |
| GET `/api/fleet/tasks/history` (+ `/history/recent`) | Archived terminal tasks |
| GET `/api/fleet/stats` (+ `/tasks/stats`) | Task/fleet stats |
| GET `/api/fleet/artifacts` · GET `/artifacts/{id}` | List / fetch artifacts |
| POST `/api/fleet/artifacts` (multipart) | Upload an artifact |
| GET `/api/fleet/cdn-manifest/{type}` · POST `/artifacts/import-cdn` | Browse CDN manifest / import artifact from CDN |
| POST `/api/fleet/artifacts/{id}/toggle-enabled` | Enable/disable an artifact |
| GET `/api/fleet/devices` (+ `/devices/paginated`) | Local device summary list |
| GET `/api/fleet/captures` | Network capture jobs |
| GET `/api/fleet/modules/summary` | Agent module summary |

**`PatchWindowController` (`/api/fleet/patch-windows`)**: GET `/`, GET `/{id}`, POST `/`, PUT `/{id}`, DELETE `/{id}`, GET `/{id}/atms`, PUT/DELETE `/{windowId}/atms/{atmId}`.

**`InternalFleetController` (`/api/internal/fleet`, ROLE_INTERNAL)** — the agent work loop:

| Method Path | Purpose |
|-------------|---------|
| GET `/api/internal/fleet/tasks/next` | Agent polls for next task |
| GET `/api/internal/fleet/tasks/{taskId}/download` | Download task artifact/payload |
| POST `/api/internal/fleet/tasks/{taskId}/status` | Report task progress/status |
| POST `/api/internal/fleet/tasks/{taskId}/verify` | Verify step |
| POST `/api/internal/fleet/tasks/{taskId}/upload` | Upload task result (e.g. capture/logs) |

## Gotchas

- **Two auth planes.** User/admin endpoints use JWT (`JWT_SECRET`) with role checks; most reads allow `hasAnyRole('USER','CUSTOMER','ADMIN','MSP_ADMIN','BCOS_ADMIN')`, mutations require `ADMIN`/`MSP_ADMIN`/`BCOS_ADMIN`. Internal endpoints require `hasRole('INTERNAL')`, granted via `X-Internal-Secret` (`INTERNAL_SECRET` env, default `dev-internal-secret`) — agent-proxy is the only intended caller.
- **Approver gate.** Approve/reject require `hasAuthority('FLEET_APPROVER') or hasRole('BCOS_ADMIN')`; `FLEET_APPROVER` comes from the `fleetTaskApprover` boolean JWT claim.
- **No long-term task store here.** Terminal tasks move to `fleet_task_history`; the durable device-facing record lives in devices. Fleet publishes `archived`/status events on `hiveops.fleet.task.events` for downstream consumers.
- **device_summary is a mirror, not source of truth.** Populated only from `hiveops.device.events`; stale if devices stops publishing (no reconciler). `auto-offset-reset=earliest`.
- **DB_URL env var** overrides the datasource URL (default `jdbc:postgresql://localhost:5432/hiveiq_fleet`); production points it at `hiveiq-postgres`.
- **Artifact CDN import** (`/artifacts/import-cdn`, `cdn.token`/`CDN_TOKEN`) is preferred over local multipart uploads for software tasks — CDN-imported artifacts carry `cdn_url` so agents download directly from the CDN.
- **Script signing:** `SCRIPT_SIGNING_PRIVATE_KEY` (base64 PKCS8 RSA) signs agent scripts (V3 migration `add_agent_scripts`).
- Multipart upload limit 100MB; `management` exposes only `health,info`.
