feat: implement update-service v1 migration and enrollment flow

- added /update/enroll endpoint and enrollment logic
- migrated update client to v1 api endpoints and bearer auth
- implemented remote status reporting in backend and scripts
- updated requirements and project status
This commit is contained in:
2025-12-31 00:05:46 +01:00
parent fde2825112
commit 47290d2d8f
41 changed files with 1384 additions and 19 deletions

View File

@ -0,0 +1,57 @@
openapi: 3.0.3
info:
title: Update Webservice API
version: 0.1.0
servers:
- url: https://update.wlkns.org
- url: https://staging.update.wlkns.org
security:
- bearerAuth: []
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
Manifest:
$ref: './openapi/schemas/manifest.yaml'
StatusReport:
$ref: './openapi/schemas/status-report.yaml'
UploadResponse:
$ref: './openapi/schemas/upload-response.yaml'
EnrollRequest:
$ref: './openapi/schemas/enroll-request.yaml'
EnrollResponse:
$ref: './openapi/schemas/enroll-response.yaml'
EnrollmentToken:
$ref: './openapi/schemas/enrollment-token.yaml'
EnrollmentTokenCreateRequest:
$ref: './openapi/schemas/enrollment-token-create-request.yaml'
EnrollmentTokenCreateResponse:
$ref: './openapi/schemas/enrollment-token-create-response.yaml'
Error:
$ref: './openapi/schemas/error.yaml'
Limits:
$ref: './openapi/schemas/limits.yaml'
LimitsPolicy:
$ref: './openapi/schemas/limits-policy.yaml'
paths:
/v1/enroll:
$ref: './openapi/paths/enroll.yaml'
/v1/admin/enrollment-tokens:
$ref: './openapi/paths/admin-enrollment-tokens.yaml'
/v1/admin/enrollment-tokens/{token_id}/revoke:
$ref: './openapi/paths/admin-enrollment-tokens-revoke.yaml'
/v1/admin/enrollment-tokens/{token_id}/export:
$ref: './openapi/paths/admin-enrollment-tokens-export.yaml'
/v1/projects/{project_id}/manifest:
$ref: './openapi/paths/manifest.yaml'
/v1/projects/{project_id}/releases/{version}/artifact:
$ref: './openapi/paths/artifact.yaml'
/v1/projects/{project_id}/status:
$ref: './openapi/paths/status.yaml'
/v1/projects/{project_id}/releases:
$ref: './openapi/paths/releases.yaml'
/v1/limits:
$ref: './openapi/paths/limits.yaml'

View File

@ -0,0 +1,31 @@
get:
summary: Export enrollment token
x-auth-scopes: [admin]
parameters:
- name: token_id
in: path
required: true
schema:
type: string
responses:
'200':
description: Token file
content:
text/plain:
schema:
type: string
example: enroll_6f3d2c...
'401':
description: Unauthorized
x-error-codes: [unauthorized]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
'404':
description: Not Found
x-error-codes: [not_found]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'

View File

@ -0,0 +1,30 @@
post:
summary: Revoke enrollment token
x-auth-scopes: [admin]
parameters:
- name: token_id
in: path
required: true
schema:
type: string
responses:
'200':
description: Revoked
content:
application/json:
schema:
$ref: '../schemas/enrollment-token.yaml'
'401':
description: Unauthorized
x-error-codes: [unauthorized]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
'404':
description: Not Found
x-error-codes: [not_found]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'

View File

@ -0,0 +1,69 @@
get:
summary: List enrollment tokens
x-auth-scopes: [admin]
parameters:
- name: project_id
in: query
required: false
schema:
type: string
- name: client_id
in: query
required: false
schema:
type: string
- name: status
in: query
required: false
schema:
type: string
enum: [active, used, expired, revoked]
responses:
'200':
description: Token list
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
$ref: '../schemas/enrollment-token.yaml'
'401':
description: Unauthorized
x-error-codes: [unauthorized]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
post:
summary: Create enrollment token
x-auth-scopes: [admin]
requestBody:
required: true
content:
application/json:
schema:
$ref: '../schemas/enrollment-token-create-request.yaml'
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '../schemas/enrollment-token-create-response.yaml'
'400':
description: Bad Request
x-error-codes: [invalid_payload]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
'401':
description: Unauthorized
x-error-codes: [unauthorized]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'

View File

@ -0,0 +1,58 @@
get:
summary: Download artifact
x-auth-scopes: [read_manifest]
parameters:
- name: project_id
in: path
required: true
schema:
type: string
- name: version
in: path
required: true
schema:
type: string
responses:
'200':
description: Artifact tar.gz
content:
application/gzip:
schema:
type: string
format: binary
'401':
description: Unauthorized
x-error-codes: [unauthorized]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
unauthorized:
value:
code: unauthorized
message: Missing or invalid token
'404':
description: Not Found
x-error-codes: [not_found]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
not_found:
value:
code: not_found
message: Artifact not found
'429':
description: Too Many Requests
x-error-codes: [rate_limited]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
rate_limited:
value:
code: rate_limited
message: Too many requests

View File

@ -0,0 +1,65 @@
post:
summary: Enroll client and issue long-term token
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '../schemas/enroll-request.yaml'
examples:
enroll:
value:
project_id: demo
client_id: device-42
software_id: kiosk
enroll_token: enroll_6f3d2c...
responses:
'200':
description: Enrollment successful
content:
application/json:
schema:
$ref: '../schemas/enroll-response.yaml'
examples:
issued:
value:
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
scope: read_manifest report_status
expires_at: 2026-12-30T10:00:00Z
'400':
description: Bad Request
x-error-codes: [invalid_payload]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
invalid_payload:
value:
code: invalid_payload
message: Missing required fields
'401':
description: Unauthorized
x-error-codes: [unauthorized]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
unauthorized:
value:
code: unauthorized
message: Invalid or expired enrollment token
'409':
description: Conflict
x-error-codes: [already_enrolled]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
already_enrolled:
value:
code: already_enrolled
message: Client already enrolled

View File

@ -0,0 +1,48 @@
get:
summary: Get service limits
x-auth-scopes: [read_manifest]
responses:
'200':
description: Limits
content:
application/json:
schema:
$ref: '../schemas/limits-policy.yaml'
examples:
medium:
value:
tier: medium
limits:
upload_max_artifact_size_bytes_soft: 1073741824
upload_max_artifact_size_bytes_hard: 2147483648
read_max_requests_per_minute_soft: 300
read_max_requests_per_minute_hard: 600
upload_max_requests_per_minute_soft: 6
upload_max_requests_per_minute_hard: 12
report_max_requests_per_minute_soft: 120
report_max_requests_per_minute_hard: 240
burst_requests_per_minute: 1200
'401':
description: Unauthorized
x-error-codes: [unauthorized]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
unauthorized:
value:
code: unauthorized
message: Missing or invalid token
'429':
description: Too Many Requests
x-error-codes: [rate_limited]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
rate_limited:
value:
code: rate_limited
message: Too many requests

View File

@ -0,0 +1,47 @@
get:
summary: Get active manifest
x-auth-scopes: [read_manifest]
parameters:
- name: project_id
in: path
required: true
schema:
type: string
responses:
'200':
description: Manifest
content:
application/json:
schema:
$ref: '../schemas/manifest.yaml'
examples:
default:
value:
version: 1.2.3
artifact_url: https://update.wlkns.org/v1/projects/demo/releases/1.2.3/artifact
sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
sig_url: https://update.wlkns.org/v1/projects/demo/releases/1.2.3/signature
'401':
description: Unauthorized
x-error-codes: [unauthorized]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
unauthorized:
value:
code: unauthorized
message: Missing or invalid token
'429':
description: Too Many Requests
x-error-codes: [rate_limited]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
rate_limited:
value:
code: rate_limited
message: Too many requests

View File

@ -0,0 +1,126 @@
post:
summary: Upload release
x-auth-scopes: [upload_release]
parameters:
- name: project_id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- version
- sha256
- artifact
properties:
version:
type: string
pattern: '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$'
example: 1.2.3
sha256:
type: string
example: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
sig_url:
type: string
format: uri
description: Optional reference to a detached signature
signature:
type: string
format: binary
description: Detached signature file (optional alternative to sig_url)
key_id:
type: string
description: Public key identifier for signature verification
artifact:
type: string
format: binary
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '../schemas/upload-response.yaml'
examples:
created:
value:
version: 1.2.3
manifest_url: https://update.wlkns.org/v1/projects/demo/manifest
active: true
'400':
description: Bad Request
x-error-codes: [invalid_payload]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
invalid_payload:
value:
code: invalid_payload
message: Missing required fields
'401':
description: Unauthorized
x-error-codes: [unauthorized]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
unauthorized:
value:
code: unauthorized
message: Missing or invalid token
'409':
description: Conflict
x-error-codes: [version_exists]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
version_exists:
value:
code: version_exists
message: Version already exists
'413':
description: Payload Too Large
x-error-codes: [payload_too_large]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
payload_too_large:
value:
code: payload_too_large
message: Artifact exceeds size limit
'422':
description: Unprocessable Entity (invalid checksum/signature/version)
x-error-codes: [checksum_mismatch, signature_invalid, signature_missing, version_invalid]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
checksum_mismatch:
value:
code: checksum_mismatch
message: SHA256 does not match artifact
'429':
description: Too Many Requests
x-error-codes: [rate_limited]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
rate_limited:
value:
code: rate_limited
message: Too many requests

View File

@ -0,0 +1,84 @@
post:
summary: Report update status
x-auth-scopes: [report_status]
parameters:
- name: project_id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '../schemas/status-report.yaml'
examples:
success:
value:
project_id: demo
version: 1.2.3
status: success
timestamp: 2025-12-28T10:15:30Z
client_id: device-42
duration_ms: 2450
failure:
value:
project_id: demo
version: 1.2.3
status: failed
timestamp: 2025-12-28T10:15:30Z
client_id: device-42
reason: checksum_mismatch
error_code: checksum_mismatch
responses:
'202':
description: Accepted
'400':
description: Bad Request
x-error-codes: [invalid_payload]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
invalid_payload:
value:
code: invalid_payload
message: Missing required fields
'401':
description: Unauthorized
x-error-codes: [unauthorized]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
unauthorized:
value:
code: unauthorized
message: Missing or invalid token
'422':
description: Unprocessable Entity (invalid version or status)
x-error-codes: [version_invalid, status_invalid]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
version_invalid:
value:
code: version_invalid
message: Version does not match SemVer
'429':
description: Too Many Requests
x-error-codes: [rate_limited]
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
rate_limited:
value:
code: rate_limited
message: Too many requests

View File

@ -0,0 +1,20 @@
type: object
required:
- project_id
- client_id
- software_id
- enroll_token
properties:
project_id:
type: string
example: demo
client_id:
type: string
example: device-42
software_id:
type: string
example: kiosk
enroll_token:
type: string
description: Pre-shared, single-use enrollment token
example: enroll_6f3d2c...

View File

@ -0,0 +1,19 @@
type: object
required:
- token
- scope
properties:
token:
type: string
description: Long-term bearer token for client requests
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
scope:
type: string
description: Space-delimited scopes
example: read_manifest report_status
expires_at:
type: string
format: date-time
nullable: true
description: Null for non-expiring tokens
example: 2026-12-30T10:00:00Z

View File

@ -0,0 +1,20 @@
type: object
required:
- project_id
properties:
project_id:
type: string
example: demo
client_id:
type: string
nullable: true
example: device-42
software_id:
type: string
nullable: true
example: kiosk
expires_at:
type: string
format: date-time
nullable: true
example: 2026-12-30T10:00:00Z

View File

@ -0,0 +1,11 @@
type: object
required:
- token
- token_meta
properties:
token:
type: string
description: One-time plaintext enrollment token
example: enroll_6f3d2c...
token_meta:
$ref: './enrollment-token.yaml'

View File

@ -0,0 +1,39 @@
type: object
required:
- id
- project_id
- status
- created_at
properties:
id:
type: string
example: tok_123
project_id:
type: string
example: demo
client_id:
type: string
nullable: true
example: device-42
software_id:
type: string
nullable: true
example: kiosk
status:
type: string
enum: [active, used, expired, revoked]
example: active
expires_at:
type: string
format: date-time
nullable: true
example: 2026-12-30T10:00:00Z
created_at:
type: string
format: date-time
example: 2025-12-30T10:00:00Z
used_at:
type: string
format: date-time
nullable: true
example: 2025-12-30T10:15:00Z

View File

@ -0,0 +1,15 @@
type: object
required:
- code
- message
properties:
code:
type: string
description: Error code (e.g., unauthorized, invalid_payload, already_enrolled)
example: unauthorized
message:
type: string
example: Missing or invalid token
details:
type: object
additionalProperties: true

View File

@ -0,0 +1,10 @@
type: object
required:
- tier
- limits
properties:
tier:
type: string
enum: [small, medium, large]
limits:
$ref: './limits.yaml'

View File

@ -0,0 +1,39 @@
type: object
required:
- upload_max_artifact_size_bytes_soft
- upload_max_artifact_size_bytes_hard
- read_max_requests_per_minute_soft
- read_max_requests_per_minute_hard
- upload_max_requests_per_minute_soft
- upload_max_requests_per_minute_hard
- report_max_requests_per_minute_soft
- report_max_requests_per_minute_hard
- burst_requests_per_minute
properties:
upload_max_artifact_size_bytes_soft:
type: integer
default: 1073741824
upload_max_artifact_size_bytes_hard:
type: integer
default: 2147483648
read_max_requests_per_minute_soft:
type: integer
default: 300
read_max_requests_per_minute_hard:
type: integer
default: 600
upload_max_requests_per_minute_soft:
type: integer
default: 6
upload_max_requests_per_minute_hard:
type: integer
default: 12
report_max_requests_per_minute_soft:
type: integer
default: 120
report_max_requests_per_minute_hard:
type: integer
default: 240
burst_requests_per_minute:
type: integer
default: 1200

View File

@ -0,0 +1,24 @@
type: object
required:
- version
- artifact_url
- sha256
properties:
version:
type: string
description: SemVer string (e.g., 1.2.3)
pattern: '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$'
example: 1.2.3
artifact_url:
type: string
format: uri
example: https://update.wlkns.org/v1/projects/demo/releases/1.2.3/artifact
sha256:
type: string
description: Hex-encoded SHA256
example: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
sig_url:
type: string
format: uri
nullable: true
example: https://update.wlkns.org/v1/projects/demo/releases/1.2.3/signature

View File

@ -0,0 +1,42 @@
type: object
required:
- project_id
- version
- status
- timestamp
properties:
project_id:
type: string
example: demo
version:
type: string
example: 1.2.3
status:
type: string
enum: [success, failed, in_progress]
example: success
timestamp:
type: string
format: date-time
example: 2025-12-28T10:15:30Z
reason:
type: string
example: checksum_mismatch
client_id:
type: string
example: device-42
client_version:
type: string
example: 1.2.2
device_type:
type: string
example: kiosk
update_channel:
type: string
example: stable
duration_ms:
type: integer
example: 2450
error_code:
type: string
example: checksum_mismatch

View File

@ -0,0 +1,16 @@
type: object
required:
- version
- manifest_url
properties:
version:
type: string
example: 1.2.3
manifest_url:
type: string
format: uri
example: https://update.wlkns.org/v1/projects/demo/manifest
active:
type: boolean
description: True if release is active
example: true