feat(container): add explicit data workflows and snapshot rollback (#193)

* feat(container): add explicit data workflows and snapshot rollback

Make container upgrades and recreation data-safe by adding explicit preserve, export, import, restore, and rollback flows across the backend, SDK, and web UI.

* fix(container): resolve go lint issues

Fix formatting and lint violations introduced by the container data workflow changes so the Go CI lint job passes cleanly.
This commit is contained in:
BBQ
2026-03-06 17:57:48 +08:00
committed by GitHub
parent 47a425baf5
commit 21999b49f4
27 changed files with 2777 additions and 759 deletions
+259 -66
View File
@@ -15,45 +15,6 @@ const docTemplate = `{
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/api/oauth/mcp/callback": {
"get": {
"description": "Handles the OAuth authorization callback, exchanges code for tokens",
"tags": [
"mcp"
],
"summary": "OAuth callback handler",
"parameters": [
{
"type": "string",
"description": "Authorization code",
"name": "code",
"in": "query",
"required": true
},
{
"type": "string",
"description": "State parameter",
"name": "state",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "HTML page that closes the popup",
"schema": {
"type": "string"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/auth/login": {
"post": {
"description": "Validate user credentials and issue a JWT",
@@ -422,6 +383,12 @@ const docTemplate = `{
"name": "bot_id",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Export /data before deletion",
"name": "preserve_data",
"in": "query"
}
],
"responses": {
@@ -443,6 +410,124 @@ const docTemplate = `{
}
}
},
"/bots/{bot_id}/container/data/export": {
"post": {
"produces": [
"application/gzip"
],
"tags": [
"containerd"
],
"summary": "Export container /data as a tar.gz archive",
"parameters": [
{
"type": "string",
"description": "Bot ID",
"name": "bot_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "file"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots/{bot_id}/container/data/import": {
"post": {
"consumes": [
"multipart/form-data"
],
"tags": [
"containerd"
],
"summary": "Import a tar.gz archive into container /data",
"parameters": [
{
"type": "string",
"description": "Bot ID",
"name": "bot_id",
"in": "path",
"required": true
},
{
"type": "file",
"description": "tar.gz archive",
"name": "file",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots/{bot_id}/container/data/restore": {
"post": {
"tags": [
"containerd"
],
"summary": "Restore previously preserved data into container",
"parameters": [
{
"type": "string",
"description": "Bot ID",
"name": "bot_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots/{bot_id}/container/fs": {
"get": {
"description": "Returns metadata about a file or directory at the given container path",
@@ -1171,6 +1256,52 @@ const docTemplate = `{
}
}
},
"/bots/{bot_id}/container/snapshots/rollback": {
"post": {
"tags": [
"containerd"
],
"summary": "Rollback container to a previous snapshot version",
"parameters": [
{
"type": "string",
"description": "Bot ID",
"name": "bot_id",
"in": "path",
"required": true
},
{
"description": "Rollback payload",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.RollbackRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots/{bot_id}/container/start": {
"post": {
"tags": [
@@ -2455,6 +2586,43 @@ const docTemplate = `{
}
}
},
"/bots/{bot_id}/mcp/{id}/oauth/exchange": {
"post": {
"description": "Frontend callback page calls this to exchange the authorization code for access/refresh tokens",
"tags": [
"mcp"
],
"summary": "Exchange OAuth authorization code for tokens",
"parameters": [
{
"description": "Authorization code and state",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.oauthExchangeRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": {
"type": "boolean"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots/{bot_id}/mcp/{id}/oauth/status": {
"get": {
"description": "Returns the current OAuth status including whether tokens are available",
@@ -8150,29 +8318,6 @@ const docTemplate = `{
}
}
},
"github_com_memohai_memoh_internal_fs.FileInfo": {
"type": "object",
"properties": {
"isDir": {
"type": "boolean"
},
"modTime": {
"type": "string"
},
"mode": {
"type": "string"
},
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"size": {
"type": "integer"
}
}
},
"github_com_memohai_memoh_internal_mcp.Connection": {
"type": "object",
"properties": {
@@ -8261,6 +8406,9 @@ const docTemplate = `{
"handlers.CreateContainerRequest": {
"type": "object",
"properties": {
"restore_data": {
"type": "boolean"
},
"snapshotter": {
"type": "string"
}
@@ -8272,6 +8420,12 @@ const docTemplate = `{
"container_id": {
"type": "string"
},
"data_restored": {
"type": "boolean"
},
"has_preserved_data": {
"type": "boolean"
},
"image": {
"type": "string"
},
@@ -8297,6 +8451,12 @@ const docTemplate = `{
"container_id": {
"type": "string"
},
"display_name": {
"type": "string"
},
"runtime_snapshot_name": {
"type": "string"
},
"snapshot_name": {
"type": "string"
},
@@ -8382,7 +8542,7 @@ const docTemplate = `{
"entries": {
"type": "array",
"items": {
"$ref": "#/definitions/github_com_memohai_memoh_internal_fs.FileInfo"
"$ref": "#/definitions/handlers.FSFileInfo"
}
},
"path": {
@@ -8457,8 +8617,8 @@ const docTemplate = `{
"created_at": {
"type": "string"
},
"host_path": {
"type": "string"
"has_preserved_data": {
"type": "boolean"
},
"image": {
"type": "string"
@@ -8650,6 +8810,14 @@ const docTemplate = `{
}
}
},
"handlers.RollbackRequest": {
"type": "object",
"properties": {
"version": {
"type": "integer"
}
}
},
"handlers.SkillItem": {
"type": "object",
"properties": {
@@ -8710,6 +8878,9 @@ const docTemplate = `{
"created_at": {
"type": "string"
},
"display_name": {
"type": "string"
},
"kind": {
"type": "string"
},
@@ -8728,6 +8899,9 @@ const docTemplate = `{
"parent": {
"type": "string"
},
"runtime_snapshot_name": {
"type": "string"
},
"snapshotter": {
"type": "string"
},
@@ -8887,8 +9061,14 @@ const docTemplate = `{
"handlers.oauthAuthorizeRequest": {
"type": "object",
"properties": {
"callback_url": {
"type": "string"
},
"client_id": {
"type": "string"
},
"client_secret": {
"type": "string"
}
}
},
@@ -8900,6 +9080,17 @@ const docTemplate = `{
}
}
},
"handlers.oauthExchangeRequest": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"state": {
"type": "string"
}
}
},
"handlers.skillsOpResponse": {
"type": "object",
"properties": {
@@ -9155,6 +9346,9 @@ const docTemplate = `{
"auth_server": {
"type": "string"
},
"callback_url": {
"type": "string"
},
"configured": {
"type": "boolean"
},
@@ -9793,7 +9987,6 @@ const docTemplate = `{
"type": "object",
"properties": {
"api_key": {
"description": "masked in response",
"type": "string"
},
"base_url": {