Files
pipely/docs/daemon-api.yaml
T
siujamo e070a3fb5f feat: initial open-source release of Pipely
OTA update server designed for high-concurrency, low-bandwidth deployments.
GORM-backed PostgreSQL, JWT auth, device management, artefact versioning,
deployment rollout with gated rate-limited downloads, and a React admin panel.
2026-07-07 10:29:28 +08:00

476 lines
15 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
openapi: "3.0.3"
info:
title: OTA Manifest — Daemon API
description: |
Device-side REST API for the OTA update platform.
Each daemon (`mcd`) calls these endpoints to register, heartbeat,
poll for updates, download artefacts, and report progress.
**10 Mbps bandwidth design points:**
- Downloads are **rate-limited** (~25 KB/s per connection) and **gated** (max 50 concurrent).
- The download endpoint supports **HTTP Range** (`206 Partial Content`) for resumable transfers.
- `check-update` returns a delta patch when available to minimise bytes on the wire.
version: "1.0.0"
contact:
name: Quanta OTA Team
servers:
- url: http://localhost:8080/api/v1
description: Local development server
tags:
- name: Device
description: Registration, heartbeat, and update checking
- name: Package
description: Artefact download
- name: Deployment
description: Lifecycle status reporting
# ─────────────────────────────────────────────────────────────
# Paths
# ─────────────────────────────────────────────────────────────
paths:
# ═══ Device ═══
/devices/register:
post:
tags: [Device]
summary: Register or refresh a device
description: |
Called by the daemon on startup. If the `deviceId` already exists
this is an upsert — all fields are updated and `lastHeartbeat` is
refreshed.
operationId: registerDevice
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RegisterDeviceRequest"
example:
deviceId: "mcd-7a3f-001"
ipAddress: "192.168.1.42"
hostname: "gateway-east-01"
os: "Debian 12"
currentVersion: "1.2.0"
groupName: "east-region"
responses:
"200":
description: Device registered or updated
content:
application/json:
schema:
$ref: "#/components/schemas/Device"
example:
id: 1
deviceId: "mcd-7a3f-001"
ipAddress: "192.168.1.42"
hostname: "gateway-east-01"
os: "Debian 12"
currentVersion: "1.2.0"
status: "online"
groupName: "east-region"
lastHeartbeat: "2026-07-03T10:30:00Z"
createdAt: "2026-07-03T10:30:00Z"
updatedAt: "2026-07-03T10:30:00Z"
"400":
$ref: "#/components/responses/BadRequest"
/devices/heartbeat:
post:
tags: [Device]
summary: Send periodic heartbeat
description: |
Called by the daemon every 3060 s. Keeps the device marked `online`
and updates the `currentVersion` so the server always knows which
version each device is running.
operationId: heartbeat
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/HeartbeatRequest"
example:
deviceId: "mcd-7a3f-001"
currentVersion: "1.2.0"
status: "online"
responses:
"200":
description: Heartbeat acknowledged
content:
application/json:
schema:
$ref: "#/components/schemas/StatusResponse"
"400":
$ref: "#/components/responses/BadRequest"
/devices/check-update:
get:
tags: [Device]
summary: Check whether an update is pending for this device
description: |
Polled by the daemon on a configurable interval (e.g. every 5 min).
When `hasUpdate` is `true` the daemon should proceed to download the
referenced artefact and then POST to `/deployments/{id}/status` as the
install progresses.
operationId: checkUpdate
parameters:
- name: deviceId
in: query
required: true
schema:
type: string
description: The device's unique identifier
example: "mcd-7a3f-001"
responses:
"200":
description: Update status for the device
content:
application/json:
schema:
$ref: "#/components/schemas/CheckUpdateResponse"
examples:
hasUpdate:
summary: Update available
value:
hasUpdate: true
artefactId: 5
version: "1.3.0"
fileSize: 4194304
sha256Hash: "a1b2c3d4e5f6..."
downloadUrl: "/api/v1/packages/download/5"
noUpdate:
summary: No update needed
value:
hasUpdate: false
"400":
$ref: "#/components/responses/BadRequest"
# ═══ Package ═══
/packages/download/{id}:
get:
tags: [Package]
summary: Download an artefact (full or delta patch)
description: |
Serves the artefact file with **per-connection bandwidth throttling**
(~25 KB/s). Supports **HTTP Range** requests so interrupted downloads
can be resumed via `Range: bytes=<offset>-`.
**Headers returned:**
- `Content-Disposition: attachment; filename="<name>"`
- `X-Checksum-SHA256: <hex>` — validate the file after download
- `Accept-Ranges: bytes`
- `Content-Length: <bytes>`
**Query parameters** are optional but recommended — they let the
server track each device's download progress.
operationId: downloadPackage
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int64
description: Artefact ID (from `check-update` response)
example: 5
- name: deviceId
in: query
required: false
schema:
type: string
description: Device identifier for progress tracking
example: "mcd-7a3f-001"
- name: deploymentId
in: query
required: false
schema:
type: integer
format: int64
description: Deployment ID for progress tracking
example: 3
responses:
"200":
description: Full artefact file
headers:
Content-Disposition:
schema:
type: string
description: Attachment filename
X-Checksum-SHA256:
schema:
type: string
description: SHA-256 hex digest of the file
Accept-Ranges:
schema:
type: string
example: bytes
content:
application/octet-stream:
schema:
type: string
format: binary
"206":
description: Partial content (resumed download)
headers:
Content-Range:
schema:
type: string
example: "bytes 1048576-4194303/4194304"
content:
application/octet-stream:
schema:
type: string
format: binary
"404":
$ref: "#/components/responses/NotFound"
"503":
description: Server at download capacity — retry later
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
error: "Server at capacity. Please retry later."
# ═══ Deployment ═══
/deployments/{id}/status:
post:
tags: [Deployment]
summary: Report deployment status for a single device
description: |
Called by the daemon at each lifecycle transition:
1. `downloading` — started the download
2. `downloaded` — download complete (may be omitted for small files)
3. `verifying` — checking SHA-256 hash
4. `installing` — applying the update
5. `completed` — update applied, device running new version
6. `failed` — unrecoverable error (set `error` field)
The server tracks these transitions per-device so operators can
monitor rollout progress.
operationId: updateDeploymentStatus
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int64
description: Deployment ID
example: 3
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/DeploymentStatusRequest"
examples:
downloading:
value:
deviceId: "mcd-7a3f-001"
status: "downloading"
completed:
value:
deviceId: "mcd-7a3f-001"
status: "completed"
failed:
value:
deviceId: "mcd-7a3f-001"
status: "failed"
error: "SHA256 mismatch: expected a1b2.. got x9y0.."
responses:
"200":
description: Status recorded
content:
application/json:
schema:
$ref: "#/components/schemas/StatusResponse"
"400":
$ref: "#/components/responses/BadRequest"
# ─────────────────────────────────────────────────────────────
# Component Schemas
# ─────────────────────────────────────────────────────────────
components:
schemas:
# -- Requests --
RegisterDeviceRequest:
type: object
required: [deviceId]
properties:
deviceId:
type: string
maxLength: 256
description: Unique device identifier (set by daemon config)
ipAddress:
type: string
description: Device IP at registration time
hostname:
type: string
description: OS hostname
os:
type: string
description: Operating system name and version
currentVersion:
type: string
description: Currently installed client version
groupName:
type: string
description: Logical group for targeted rollouts (defaults to "default")
HeartbeatRequest:
type: object
required: [deviceId]
properties:
deviceId:
type: string
maxLength: 256
currentVersion:
type: string
description: Currently running version
status:
type: string
enum: [online, offline, upgrading]
default: online
DeploymentStatusRequest:
type: object
required: [deviceId, status]
properties:
deviceId:
type: string
description: Device identifier
status:
type: string
enum:
- downloading
- downloaded
- verifying
- installing
- completed
- failed
error:
type: string
description: Error description (only meaningful when status = "failed")
# -- Responses --
Device:
type: object
properties:
id:
type: integer
format: int64
deviceId:
type: string
ipAddress:
type: string
hostname:
type: string
os:
type: string
currentVersion:
type: string
status:
type: string
enum: [online, offline, upgrading]
groupName:
type: string
lastHeartbeat:
type: string
format: date-time
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
CheckUpdateResponse:
type: object
properties:
hasUpdate:
type: boolean
description: Whether an update is pending
artefactId:
type: integer
format: int64
description: Artefact ID to pass to the download endpoint
version:
type: string
description: Target version string
fileSize:
type: integer
format: int64
description: Total file size in bytes
sha256Hash:
type: string
description: Expected SHA-256 hex digest (validate after download)
downloadUrl:
type: string
description: Download path relative to the server
StatusResponse:
type: object
properties:
status:
type: string
example: "acknowledged"
ErrorResponse:
type: object
properties:
error:
type: string
description: Human-readable error message
# -- Reusable responses --
responses:
BadRequest:
description: Invalid request body or parameters
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
error: "deviceId is required"
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
error: "Artefact not found"
# ─────────────────────────────────────────────────────────────
# Daemon update flow (non-normative)
# ─────────────────────────────────────────────────────────────
x-daemon-flow:
polling:
interval: "5m (configurable)"
description: |
1. **Startup** → `POST /devices/register`
2. **Every N s** → `POST /devices/heartbeat`
3. **Every 5 min** → `GET /devices/check-update?deviceId=xxx`
4. **If hasUpdate==true** →
a. `POST /deployments/{id}/status` → `downloading`
b. `GET /packages/download/{artefactId}?deviceId=xxx&deploymentId={id}`
- Use `Range: bytes=<offset>-` to resume interrupted transfers
c. Verify SHA-256 hash against `sha256Hash` from step 3
d. `POST /deployments/{id}/status` → `verifying`
e. `POST /deployments/{id}/status` → `installing`
f. Restart the Java process, confirm health
g. `POST /deployments/{id}/status` → `completed`
h. `POST /devices/heartbeat` with the new `currentVersion`
- **On any failure** → `POST /deployments/{id}/status` → `failed`