---
module: fleet.artifacts
title: Artifact Import Guide
tab: Internal
order: 20
audience: internal
---

> Ops / support view. Artifacts are the packages (agents, hotfixes, third-party software, scripts) that fleet tasks deliver to devices. Owned by **hiveops-fleet** (`com.hiveops.fleet`, port 8097, DB `hiveiq_fleet`, table `fleet_artifacts`). UI: `FleetArtifacts.svelte`.

## Who can do what (roles)

Read + mutate roles differ — a `USER`/`CUSTOMER` can *see* artifacts but cannot import, delete, or toggle them.

| Action | Endpoint | `@PreAuthorize` |
|--------|----------|-----------------|
| List / view artifacts | `GET /api/fleet/artifacts`, `GET /api/fleet/artifacts/{id}` | `hasAnyRole('USER','CUSTOMER','ADMIN','MSP_ADMIN','BCOS_ADMIN')` |
| Browse CDN manifest | `GET /api/fleet/cdn-manifest/{type}` | `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` |
| Import from CDN | `POST /api/fleet/artifacts/import-cdn` | `hasAnyRole('ADMIN','MSP_ADMIN','BCOS_ADMIN')` |
| Upload (multipart) | `POST /api/fleet/artifacts` | `hasAnyRole('ADMIN','MSP_ADMIN','BCOS_ADMIN')` |
| Enable / disable | `POST /api/fleet/artifacts/{id}/toggle-enabled` | `hasAnyRole('ADMIN','MSP_ADMIN','BCOS_ADMIN')` |
| Delete | `DELETE /api/fleet/artifacts/{id}` | `hasAnyRole('ADMIN','MSP_ADMIN','BCOS_ADMIN')` |

JWT is validated from `JWT_SECRET`. There is no `FLEET_APPROVER` requirement on artifacts — approval only gates task execution, not import.

## Two ways an artifact exists

1. **CDN-imported** (preferred) — `Import Agent` / `Import Hotfix` / `Import Software` buttons. Fleet does a `HEAD` probe against the CDN URL for file size only; **the file is never downloaded to the fleet server**. The row stores `cdn_url` and the agent pulls the bytes straight from `cdn.bcos.cloud` at task time. No `sha256_hash`, no `storage_path`.
2. **Multipart upload** — `POST /artifacts` streams the file to disk (`FLEET_UPLOAD_DIR`, default `/app/uploads/fleet`), computes SHA-256, and stores `storage_path`. The agent then downloads it in chunks from fleet's internal endpoint.

The table shows a **SHA-256** column: populated for uploads, blank/`-` for CDN imports (expected, not a bug).

## Artifact types (enum `ArtifactType`)

`UPDATE_AGENT` · `UPDATE_HOTFIX` · `UPDATE_SOFTWARE` · `UPDATE_MODULE` · `SCRIPT`

The UI relabels these: Agent Patch / Hotfix / Software Update / Module Update / Script. The `artifactType` chosen at import determines which fleet-task kind can deliver it.

## First things a support engineer checks

- **"Import button does nothing / error loading manifest"** → the CDN manifest fetch failed. Fleet reads:
  - agent → `https://cdn.bcos.cloud/downloads/agent/latest.json`
  - hotfix → `https://cdn.bcos.cloud/downloads/hotfix/latest.json`
  - software → `https://cdn.bcos.cloud/downloads/software/latest.json`
  Check `CDN_TOKEN` env is set on the fleet container — it's sent as the `X-CDN-Token` header. A 401/403 from the CDN means a missing/stale token.
- **"Artifact imported but device task fails to download"** → confirm the artifact is **Enabled** (`enabled` defaults true, but a disabled artifact stays selectable in old task flows). Toggle it on. For CDN artifacts also verify the device itself can reach `cdn.bcos.cloud` (download bypasses fleet entirely, so `download_offset` stays 0 — that is normal, not a stall).
- **"Can't delete artifact — 409 Conflict"** → an active task (`PENDING`/`QUEUED`/`RUNNING`) still references it. The delete is intentionally blocked. Cancel/drain the referencing task first, then delete. Terminal-task references are auto-cleared on delete.
- **"Wrong institution sees / can't see an artifact"** → check the `Institution` vs `Global` badge. An artifact with a blank `institutionKey` shows **Global** (all institutions); a set key scopes it. `institution_key` is capped at 10 chars.
- **Upload rejected as too large** → multipart limit is **100 MB** (`spring.servlet.multipart.max-file-size`). Large third-party packages must go via CDN import, not multipart.

## Common failure modes → fix

| Symptom | Likely cause | Fix |
|---------|-------------|-----|
| Manifest panel shows "Failed to load CDN manifest" | `CDN_TOKEN` missing/wrong, or CDN unreachable | Set/rotate `CDN_TOKEN`; curl the `latest.json` with `X-CDN-Token` from the fleet host |
| Agent gets `410 GONE` on task download | Task points at a CDN artifact but agent tried fleet's chunk endpoint | CDN artifacts must download from `cdnUrl` directly; check agent version supports `artifactCdnUrl` |
| Import "Module" manifest fails | `/cdn-manifest/modules` is **not** a supported type server-side (only agent/hotfix/software) | Don't rely on a modules manifest; import module packages another way |
| Delete returns 409 | Active task references the artifact | Cancel the task, retry delete |
| Software list empty | Nothing published to the software manifest yet | Upload via Browser → File → Upload Software, then Refresh |

## Where things live

- Uploaded files: `FLEET_UPLOAD_DIR` (default `/app/uploads/fleet`) on the fleet container.
- DB row: `fleet_artifacts` in `hiveiq_fleet` (Postgres on `10.10.10.188` via ProxyJump in prod).
- Logs: fleet container / Loki — `FleetArtifactService` logs every upload, CDN registration, and delete.
