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:
@ -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'
|
||||
@ -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'
|
||||
69
docs/architecture/openapi/paths/admin-enrollment-tokens.yaml
Normal file
69
docs/architecture/openapi/paths/admin-enrollment-tokens.yaml
Normal 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'
|
||||
58
docs/architecture/openapi/paths/artifact.yaml
Normal file
58
docs/architecture/openapi/paths/artifact.yaml
Normal 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
|
||||
65
docs/architecture/openapi/paths/enroll.yaml
Normal file
65
docs/architecture/openapi/paths/enroll.yaml
Normal 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
|
||||
48
docs/architecture/openapi/paths/limits.yaml
Normal file
48
docs/architecture/openapi/paths/limits.yaml
Normal 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
|
||||
47
docs/architecture/openapi/paths/manifest.yaml
Normal file
47
docs/architecture/openapi/paths/manifest.yaml
Normal 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
|
||||
126
docs/architecture/openapi/paths/releases.yaml
Normal file
126
docs/architecture/openapi/paths/releases.yaml
Normal 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
|
||||
84
docs/architecture/openapi/paths/status.yaml
Normal file
84
docs/architecture/openapi/paths/status.yaml
Normal 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
|
||||
Reference in New Issue
Block a user