# SSE 事件

本頁說明 go-pve-qemu 長時間操作所使用的 Server-Sent Events 格式，以及如何在 Go 中消費。

使用 SSE 的端點：`install`、`start`、`reboot`、`set/node`。

## 事件格式

```text
data: {"step":"<階段>","status":"<狀態>","message":"<訊息>"}

event: close
data: {}
```

| 欄位 | 說明 |
|---|---|
| `step` | 步驟名稱；安裝流程為 `<階段> > <步驟>`，見 [佈建流程](/zh/provisioning-pipeline) |
| `status` | 見下表 |
| `message` | 人類可讀訊息；成功步驟附耗時，例如 `(3.21s)` |

| `status` | 意義 |
|---|---|
| `processing` | 步驟進行中，或 `qm`／初始化腳本的逐行輸出（行首兩個空白） |
| `success` | 步驟完成 |
| `info` | 結束提示（start／reboot） |
| `error` | 步驟失敗，流程中止 |

| `message` 前綴 | 意義 |
|---|---|
| `[*]` | 資訊 |
| `[+]` | 成功 |
| `[-]` | 失敗 |

## 注意事項

- 回應一律為 HTTP 200，成功與否需讀取事件判斷
- `message` 以字串拼接輸出、未做 JSON 跳脫；指令輸出含 `"` 或 `\` 時該行可能無法以 JSON 解析，用戶端應略過解析失敗的行
- 前置檢查失敗（`ALLOW_IPS`、VM 狀態）時只會收到一行 `data: {"message": "..."}`，沒有 `step`／`status`
- 用戶端中斷連線後，伺服器端流程繼續執行，只是不再推送事件

## Go 用戶端範例

```go
package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
)

type event struct {
	Step    string `json:"step"`
	Status  string `json:"status"`
	Message string `json:"message"`
}

func main() {
	body, err := json.Marshal(map[string]any{
		"os":      "ubuntu",
		"version": "24.04",
		"cpu":     2,
		"ram":     4096,
		"disk":    "32G",
	})
	if err != nil {
		log.Fatal(err)
	}

	resp, err := http.Post("http://192.168.0.11:8080/api/vm/install", "application/json", bytes.NewReader(body))
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		log.Fatalf("install rejected: %s", resp.Status)
	}

	failed := false
	scanner := bufio.NewScanner(resp.Body)
	for scanner.Scan() {
		line := scanner.Text()
		if line == "event: close" {
			// 伺服器端流程結束
			break
		}
		payload, ok := strings.CutPrefix(line, "data: ")
		if !ok {
			continue
		}
		var e event
		if err := json.Unmarshal([]byte(payload), &e); err != nil {
			// 未跳脫的指令輸出，略過
			continue
		}
		fmt.Printf("[%s] %s %s\n", e.Status, e.Step, e.Message)
		if e.Status == "error" {
			failed = true
		}
	}
	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}
	if failed {
		log.Fatal("install failed")
	}
}
```
