Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ let text = try MCSMResponseEnvelope.checkedText(raw) // 或 unwrapText(raw)
| `GET /api/protected_instance/open` | 实例的主人(**普通用户也行**) | 启动。面板立即返回,状态随后变化 |
| `GET /api/protected_instance/stop` | 同上 | 停止(发送 `stop`,不强制杀进程) |
| `GET /api/protected_instance/restart` | 同上 | 重启 |
| `GET /api/protected_instance/kill` | 同上 | **强制终止**(守护进程直接杀进程,不给保存机会)。面板侧超时 30 秒 |
| `GET /api/protected_instance/command` | 同上 | 命令走查询参数;`/` 开头是 MC 命令,不带 `/` 是控制台命令 |
| `GET /api/protected_instance/outputlog` | 同上 | 返回 `text/plain` 的**日志尾部**,可能从半行开始。体里仍是 `{status,data,time}` 外壳 → 用 `MCSMResponseEnvelope` 解 |
| `POST /api/protected_instance/stream_channel` | 同上 | 控制台**实时流**的第一步:拿 `addr` + 一次性口令,然后连那台 socket.io 发 `stream/auth`。见下节 |
Expand Down
56 changes: 55 additions & 1 deletion Sources/MCSMAPI/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,10 @@ paths:
operationId: stopInstance
summary: 停止实例
description: |
对应面板「停止」(会给实例发 `stop`,等它自己退出)。要强制杀进程用 `kill`(本包未封装)。
对应面板「停止」(会给实例发 `stop`,等它自己退出)。要强制杀进程用 `kill`。

⚠️ **停止是异步的**:面板立即返回 200,实例随后进入 `STOPPING`;服务器卡住/存档很慢时
可能长时间停在 `STOPPING`,此时用 `kill` 强杀。

**普通用户也能调用**(`/protected_instance` 一组只要"这台实例属于你",不要求管理员)。
面板对成功/失败都返回 200 + 空的 JSON 对象或节点回执,所以本 spec 不声明 200 的响应体:
Expand Down Expand Up @@ -313,6 +316,57 @@ paths:
text/plain:
schema:
type: string
/api/protected_instance/kill:
get:
operationId: killInstance
summary: 强制终止实例(强杀进程)
description: |
对应面板的「强制终止」:守护进程直接 `kill` 掉服务器进程,**不给服务器保存/退出的机会**
(没存盘的进度会丢)。用于"停止卡住"的场合。

面板源码:`panel/src/app/routers/instance_operate_router.ts` 的 `/kill`(`router.all`,
前端用 `GET`)→ 守护进程 `instance/kill` → `instance.execPreset("kill")`;
面板侧超时 `INSTANCE_KILL_TIMEOUT = 30000`(30 秒,注意比普通动作长)。

官方 apidoc 已收录(`api_instance.md` 的 `GET /api/protected_instance/kill`)。
权限与其它 `protected_instance` 端点一致:**普通用户也能调**(只要求这台实例属于你)。
parameters:
- name: daemonId
in: query
required: true
schema:
type: string
- name: uuid
in: query
required: true
description: 实例 UUID(**不是**昵称)
schema:
type: string
responses:
"200":
# 只声明"受理了",不声明 200 的响应体:面板回的是 `{status,data,time}` 外壳,
# 里面的 `data` 结构各版本不一致(实测 10.18.0 是 `{instanceUuid}`),
# 声明成具体 schema 反而会因为多/少字段解码失败(踩过:typeMismatch String/dictionary)。
# 我们只关心"请求有没有被拒绝"(403/500),与 `stop` / `restart` 一致。
description: 已受理(面板立即返回;实例状态随后变化,需重新查询)
"403":
description: API Key 权限不足 / 未开启 API Key 功能 / 不是这台实例的主人
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
text/plain:
schema:
type: string
"500":
description: 面板内部错误 / 节点(守护进程)返回了错误
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
text/plain:
schema:
type: string
/api/protected_instance/restart:
get:
operationId: restartInstance
Expand Down
3 changes: 3 additions & 0 deletions Tests/MCSMAPITests/ClientRequestTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ final class ClientRequestTests {
("openInstance", "/api/protected_instance/open"),
("stopInstance", "/api/protected_instance/stop"),
("restartInstance", "/api/protected_instance/restart"),
("killInstance", "/api/protected_instance/kill"),
] {
let capture = RequestCapture()
let client = makeClient(capture: capture)
Expand All @@ -57,6 +58,8 @@ final class ClientRequestTests {
_ = try await client.openInstance(query: .init(daemonId: "d1", uuid: "i1"))
case "stopInstance":
_ = try await client.stopInstance(query: .init(daemonId: "d1", uuid: "i1"))
case "killInstance":
_ = try await client.killInstance(query: .init(daemonId: "d1", uuid: "i1"))
default:
_ = try await client.restartInstance(query: .init(daemonId: "d1", uuid: "i1"))
}
Expand Down
Loading