feat: signed releases and a host-side updater for container nodes

CI signs the image digest of each agent and probe release; the panel only
stores and forwards it. Nodes write the manifest to their data directory and
a host updater verifies the signature with a local public key, pulls by
digest, restarts the unit and rolls back when it is unhealthy. The panel can
list releases, ask a node to update and show the reported update state.
This commit is contained in:
justme committed 2026-10-06 22:37:26 +08:00
1 parent 48072737c9
commit 0a66f5e07a
26 files changed
+1384 -215

No files matched your search

+297 -1
View File
@@ -3696,7 +3696,7 @@
"tags": [
"admin-build"
],
"summary": "Version, tag, commit and build time of the backend and every agent and probe",
"summary": "Version, tag, commit, build time and update state of the backend and every agent and probe",
"operationId": "build_info_get",
"responses": {
"200": {
@@ -3732,6 +3732,175 @@
}
}
},
"/api/admin/releases": {
"get": {
"tags": [
"admin-build"
],
"summary": "The latest signed release manifest of each component",
"operationId": "releases_list",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PageDtoOfReleaseDto"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/ProblemDto"
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/ProblemDto"
}
}
}
}
}
},
"post": {
"tags": [
"admin-build"
],
"summary": "Register a CI-signed release manifest; online nodes get it right away (their updaters verify the signature)",
"operationId": "releases_publish",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PublishReleaseRequest"
}
}
},
"required": true
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ReleaseDto"
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/ProblemDto"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/ProblemDto"
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/ProblemDto"
}
}
}
},
"409": {
"description": "Conflict",
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/ProblemDto"
}
}
}
}
}
}
},
"/api/admin/nodes/{id}/update": {
"post": {
"tags": [
"admin-build"
],
"summary": "Ask an online agent or probe to install the release it has been offered",
"operationId": "nodes_update",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "No Content",
"content": {
"application/json": {
"schema": { }
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/ProblemDto"
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/ProblemDto"
}
}
}
},
"409": {
"description": "Conflict",
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/ProblemDto"
}
}
}
}
}
}
},
"/healthz": {
"get": {
"tags": [
@@ -5540,6 +5709,16 @@
"null",
"string"
]
},
"update": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/NodeUpdateDto"
}
]
}
}
},
@@ -5612,6 +5791,49 @@
}
}
},
"NodeUpdateDto": {
"required": [
"state",
"current_digest",
"available_tag",
"available_version",
"error",
"at"
],
"type": "object",
"properties": {
"state": {
"type": "string"
},
"current_digest": {
"type": [
"null",
"string"
]
},
"available_tag": {
"type": [
"null",
"string"
]
},
"available_version": {
"type": [
"null",
"string"
]
},
"error": {
"type": [
"null",
"string"
]
},
"at": {
"format": "date-time"
}
}
},
"OverviewDto": {
"required": [
"range",
@@ -5989,6 +6211,27 @@
}
}
},
"PageDtoOfReleaseDto": {
"required": [
"items",
"next_cursor"
],
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ReleaseDto"
}
},
"next_cursor": {
"type": [
"null",
"string"
]
}
}
},
"PageDtoOfSettingDto": {
"required": [
"items",
@@ -6432,6 +6675,27 @@
}
}
},
"PublishReleaseRequest": {
"required": [
"manifest",
"signature"
],
"type": "object",
"properties": {
"manifest": {
"type": [
"null",
"string"
]
},
"signature": {
"type": [
"null",
"string"
]
}
}
},
"RawChunkMetaDto": {
"required": [
"chunk_id",
@@ -6573,6 +6837,38 @@
}
}
},
"ReleaseDto": {
"required": [
"component",
"seq",
"tag",
"version",
"digest"
],
"type": "object",
"properties": {
"component": {
"type": "string"
},
"seq": {
"pattern": "^-?(?:0|[1-9]\\d*)$",
"type": [
"integer",
"string"
],
"format": "int64"
},
"tag": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string"
}
}
},
"ScoreDto": {
"required": [
"score",