เจ้าหน้าที่ AI ควรดึงข้อมูล https://remote.ivjn.us/SKILL.md.

# HTTP API สำหรับการดำเนินการ Agent

> HTML: https://remote.ivjn.us/docs/th/http-api/

ขณะนี้การดำเนินการที่ปิดผนึกแบบเนทีฟรองรับผู้เรียก HTTP เท่านั้น ใช้ `remote-cli --transport http` หรือ `REMOTE_TRANSPORT=http` `POST /api/sealed/connections` สร้างกล่องจดหมาย `POST /input` ส่งการจับมือหรือไซเฟอร์เท็กซ์ที่ลงนามแล้ว ผลการสำรวจความคิดเห็น `GET /events` และ `DELETE` ปิดการเชื่อมต่อ ลูกค้ายังคงรักษาการเข้ารหัสแบบ end-to-end และการอนุญาตอุปกรณ์ที่มีอยู่ จุดสิ้นสุดการดำเนินการของอุปกรณ์เดิมยังคงส่งคืน 409 การเชื่อมต่ออุปกรณ์ยังคงไม่เปลี่ยนแปลง กล่องจดหมาย HTTP ต้องการความเกี่ยวข้องกับการสร้างอินสแตนซ์ Exchange และหมดอายุหลังจาก 90 วินาทีโดยไม่มีกิจกรรม ทำให้อินพุตเป็นอนุกรม ลองเฉพาะลำดับและข้อความไซเฟอร์เท็กซ์เดียวกันอีกครั้ง และรับทราบเหตุการณ์ที่ประมวลผลด้วย `after` เว็บ Console, อุปกรณ์พกพา และ LAN ยังคงรักษาการขนส่งที่มีอยู่ การดำเนินการที่กำหนดไว้ล่วงหน้ายังคงต้องใช้ `manage`

https://remote.ivjn.us/sealed-http.md


HTTP `409` / `Sealed sessions are required.` จากปลายทางการทำงานของอุปกรณ์หมายความว่าต้องใช้เซสชันที่ปิดผนึก โทเค็น Bearer เพียงอย่างเดียวไม่สามารถสั่งงานอุปกรณ์ได้ ใช้ `remote-cli --profile alice exec lab -- uname -a` หรือ `remote-cli --profile alice --transport http exec lab -- uname -a` ในสภาพแวดล้อมที่ใช้ได้เฉพาะ HTTP โดย CLI ยังคงดำเนินการจับมือที่ลงนามและการเข้ารหัสจากต้นทางถึงปลายทาง

## URL ฐาน

การผลิต Exchange:

```text
https://exchange.ivjn.us
```

การปรับใช้ที่โฮสต์ด้วยตนเองใช้ต้นทาง HTTPS ของคุณเอง เส้นทางทั้งหมดด้านล่างสัมพันธ์กับฐานนั้น ส่ง JSON ด้วย `Content-Type: application/json`

## การรับรองความถูกต้อง

ทุกเส้นทาง `/api/*` (ยกเว้นผู้ตรวจสอบสิทธิ์สาธารณะ/ผู้ช่วยลงทะเบียน) ต้องใช้:

```http
Authorization: Bearer <token>
```

`<token>` อาจเป็นอย่างใดอย่างหนึ่ง:

1. **โทเค็นเซสชันการเข้าสู่ระบบด้วยรหัสผ่าน** จาก `POST /api/auth/login`
2. **Operator primary API key** (โทเค็นที่มีอายุการใช้งานยาวนานจะแสดงครั้งเดียวเมื่อมีการสร้างบัญชีตัวดำเนินการ)

ทั้งสองได้รับการยอมรับโดยพาธ `authenticate_operator` เดียวกับที่ใช้โดยคอนโซลและ CLI

### รหัสผ่านเข้าสู่ระบบ

```bash
curl -sS -X POST 'https://exchange.ivjn.us/api/auth/login' \
  -H 'Content-Type: application/json' \
  -d '{"email":"alice@example.com","password":"your-password-here"}'
```

`email` คือข้อมูลประจำตัวของบัญชีและต้องเป็นที่อยู่ที่ถูกต้อง บริการทำให้เป็นมาตรฐาน (ตัดแต่ง ตัวพิมพ์เล็ก) และต้องมีการตรวจสอบที่อยู่ การใช้งานที่มีอยู่ยังคงสามารถส่ง `{ "username": "alice", "password": "..." }` ได้จนกว่าบัญชีเหล่านั้นจะย้ายไปที่อีเมล ค่าที่มี `@` ใน `username` จะถือเป็นอีเมล ที่อยู่ที่ไม่ถูกต้องจะถูกปฏิเสธก่อนการตรวจสอบรหัสผ่าน

ตัวอย่างการตอบกลับ:

```json
{
  "username": "alice",
  "email": "alice@example.com",
  "tenant_id": "00000000-0000-0000-0000-000000000000",
  "token": "<prefix>.<secret>",
  "expires_in": 2592000
}
```

ใช้ `token` เป็นหนังสือรับรองผู้ถือ โทร `POST /api/auth/logout` ด้วยส่วนหัวเดียวกันเพื่อเพิกถอนเซสชันนั้น คีย์ API ตัวดำเนินการหลักจะไม่ถูกเพิกถอนโดยการออกจากระบบ

### คีย์ Operator API (โทเค็นหลัก)

```bash
TOKEN='<prefix>.<secret>'   # issued at operator creation; store offline
curl -sS 'https://exchange.ivjn.us/api/devices' \
  -H "Authorization: Bearer ${TOKEN}"
```

## แสดงรายการอุปกรณ์

```bash
curl -sS 'https://exchange.ivjn.us/api/devices' \
  -H "Authorization: Bearer ${TOKEN}"
```

แต่ละรายการประกอบด้วย `id`, `name`, `online`, `enabled`, `info`, การประทับเวลา และ `active_sessions` สินค้าคงคลังของอุปกรณ์คือข้อมูลเมตา Exchange การลงรายการอุปกรณ์ไม่ได้สร้างเซสชันที่ปิดผนึกซึ่งจำเป็นต่อการใช้งาน

## การปฏิเสธ HTTP การกระทำของอุปกรณ์และการดูแลเซสชัน

การทำงานของอุปกรณ์ทั้งหมดใช้เซสชัน WebSocket ที่ปิดผนึก เส้นทาง HTTP ด้านล่างไม่สามารถสร้างการจับมือไคลเอ็นต์/อุปกรณ์ได้ ดังนั้นจึงส่งคืน HTTP `409` สำหรับคำขอที่ได้รับการตรวจสอบสิทธิ์และได้รับอนุญาตซึ่งผ่านการตรวจสอบความถูกต้องของคำขอ การปฏิเสธเกิดขึ้นก่อนการจัดส่งอุปกรณ์: ไม่มีการส่งคำสั่ง, คำขอเซ็นเซอร์, โพรบเครือข่าย, การอัปเดต, การเรียก ubus, คำของานที่ค้าง หรือคำสั่งที่ปักหมุดไว้จะถูกส่งไปยัง agent อุปกรณ์ออนไลน์ไม่ได้ทำให้เส้นทางเหล่านี้สามารถดำเนินการได้

การตรวจสอบสิทธิ์ การอนุญาต และการตรวจสอบอินพุตยังคงมีผลอยู่ ข้อมูลรับรองไม่ถูกต้องส่งคืน `401`; การดำเนินการที่ต้องห้ามส่งคืน `403`; คำสั่ง URL หรือเนื้อหาคำขอที่ไม่ถูกต้องยังคงมีข้อผิดพลาดในการตรวจสอบ รวมถึง `400` `409` เซสชันที่ปิดผนึกไม่ได้แทนที่การตรวจสอบความถูกต้องหรือการอนุญาตก่อนหน้านี้ที่ล้มเหลว การตรวจสอบสิทธิ์สำหรับคำสั่งที่ปักหมุดเป็นไปตามนโยบายคำสั่งที่ปักหมุด การส่งต่อยังคงไม่อนุญาตให้มีการดำเนินการ HTTP

| วิธีการและเส้นทาง | ลักษณะการทำงานหลังจากการพิสูจน์ตัวตน การอนุญาต และการตรวจสอบความถูกต้อง |
| --- | --- |
| `POST /api/devices/{id}/exec` | `409`: ดำเนินการภายในเซสชัน WebSocket ที่ปิดผนึก |
| `GET /api/devices/{id}/exec/holds` | `409`: ตรวจสอบงานที่จัดขึ้นภายในเซสชัน WebSocket ที่ปิดผนึก |
| `POST /api/devices/{id}/exec/holds/{job_id}/kill` | `409`: หยุดงานที่จัดขึ้นภายในเซสชัน WebSocket ที่ปิดผนึก |
| `GET /api/devices/{id}/exec/holds/{job_id}/cat` | `409`: อ่านเอาต์พุตงานที่เก็บไว้ภายในเซสชัน WebSocket ที่ปิดผนึก |
| `POST /api/devices/{id}/sensors` | `409`: รวบรวมเซ็นเซอร์ภายในเซสชัน WebSocket ที่ปิดสนิท |
| `POST /api/devices/{id}/net` | `409`: รันโพรบเครือข่ายภายในเซสชัน WebSocket ที่ปิดผนึก |
| `POST /api/devices/{id}/update` | `409`: ตรวจสอบหรือใช้การอัปเดตภายในเซสชัน WebSocket ที่ปิดผนึก |
| `POST /api/devices/{id}/ubus` | `409`: เรียก ubus ภายในเซสชัน WebSocket ที่ปิดผนึก |
| `POST /api/devices/{id}/commands/{cmd}/run` | `409`: ดึงคำสั่งที่ปักหมุดไว้และดำเนินการภายในเซสชัน WebSocket ที่ปิดผนึกพร้อมสิทธิ์การจัดการที่ได้รับอนุญาตจากอุปกรณ์ |

ตัวอย่างเช่น คำขอดำเนินการที่ถูกต้องนี้เป็นการตรวจสอบการปฏิเสธ ไม่ใช่อินเทอร์เฟซการดำเนินการคำสั่ง:

```bash
curl -i -X POST "https://exchange.ivjn.us/api/devices/${DEVICE_ID}/exec" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H 'Content-Type: application/json' \
  -d '{"command":"uname -a","timeout_secs":30}'
```

คาดหวัง HTTP `409` พร้อมด้วยข้อความแสดงข้อผิดพลาดภาษาอังกฤษ `Sealed sessions are required.` (แปลตามภาษาที่ร้องขอ) สร้างเซสชัน WebSocket แบบปิดผนึกดั้งเดิมเพื่อดำเนินการ ไม่มีการตอบสนองคำสั่งเอาต์พุตที่สำเร็จจากเส้นทาง HTTP นี้

### การดูแลจัดการเซสชัน Exchange

สินค้าคงคลังและการยกเลิกเซสชันยังคงใช้งานได้ผ่าน HTTP เนื่องจากจัดการข้อมูลเมตาการกำหนดเส้นทาง Exchange พวกเขาจะไม่ดำเนินการการทำงานของอุปกรณ์หรือเปิดเซสชันอุปกรณ์เพิ่มเติม

| วิธีการและเส้นทาง | ร่างกายที่ประสบความสำเร็จ |
| --- | --- |
| `GET /api/devices/{id}/sessions` | `{sessions:[DeviceSessionInfo], limit, count}` |
| `POST /api/devices/{id}/sessions/{session_id}/kill` | `{session_id, released}` |

ฟิลด์ `DeviceSessionInfo` คือ `session_id` (ID ตัวเลขฝั่งอุปกรณ์), `operator_session_id`, `kind`, `hold`, `age_secs` และ `deadline_unix_ms` `limit` คือ `max_sessions_per_device` (ค่าเริ่มต้น 32); `count` คือความยาวรายการปัจจุบัน รายการอ่านสถานะฮับ Exchange ในเครื่องโดยตรง อุปกรณ์ออฟไลน์ในเครื่องส่งคืน `503` Kill ใช้ **device-side** ID ส่งการยกเลิกที่เหมาะสม (รวมถึง exec ที่ถูกระงับ) และเผยแพร่เส้นทางในเครื่องแม้ว่าอุปกรณ์จะไม่ตอบรับก็ตาม เซสชันที่หายไปหรืออุปกรณ์ออฟไลน์ในเครื่องส่งคืน HTTP `200` พร้อมด้วย `released:false` ตัวดำเนินการแบบอ่านอย่างเดียวได้รับ `403` สำหรับจุดปลายทั้งสอง

```bash
curl -sS "https://exchange.ivjn.us/api/devices/${DEVICE_ID}/sessions" \
  -H "Authorization: Bearer ${TOKEN}"

curl -sS -X POST "https://exchange.ivjn.us/api/devices/${DEVICE_ID}/sessions/${SESSION_ID}/kill" \
  -H "Authorization: Bearer ${TOKEN}"
```

ก่อนที่จะปฏิเสธขีดจำกัดเซสชันทั้งหมด Exchange จะเรียกคืนเส้นทางของผู้ดำเนินการที่ไม่ทำงานและเส้นทางที่เลยกำหนดเวลาไปแล้ว: การหมดเวลาของ exec + 15 วินาที, การถ่ายโอนไฟล์ 3600 วินาที + 15 วินาที, ประเภทอื่นๆ 24 ชั่วโมง ผู้บริหารที่จัดขึ้นภายในกำหนดเวลาจะแยกออกจากกันเมื่อตัวดำเนินการตัดการเชื่อมต่อแทนที่จะถูกฆ่า

### การดำเนินการ CLI ที่ปิดผนึกแบบเนทีฟ

CLI สร้างเซสชันที่ปิดผนึกซึ่งเป็นส่วนหนึ่งของการทำงานของอุปกรณ์ตามปกติ ดำเนินการอนุมัติรูทพื้นที่ทำงานและการอนุญาตอุปกรณ์ก่อน โปรดดู [เซสชันที่ปิดผนึก](/docs/th/sealed-sessions.md) ไม่มีการเลือกไม่ใช้ข้อความธรรมดา คำสั่งปกติไม่จำเป็นต้องมีแฟล็กการปิดผนึกเพิ่มเติม:

```bash
remote-cli exec "$DEVICE_ID" -- uname -a
remote-cli shell "$DEVICE_ID"
remote-cli exec-holds "$DEVICE_ID"
remote-cli exec-hold-cat "$DEVICE_ID" "$JOB_ID"
remote-cli exec-hold-kill "$DEVICE_ID" "$JOB_ID"
remote-cli sensors "$DEVICE_ID" --json
remote-cli net "$DEVICE_ID" ping 1.1.1.1
remote-cli net "$DEVICE_ID" http https://example.com/
remote-cli ubus "$DEVICE_ID" system board '{}'
remote-cli commands list "$DEVICE_ID"
remote-cli commands run "$DEVICE_ID" "$COMMAND_ID"
```

การปฏิบัติงานที่จัดขึ้นจะดำเนินการภายในเซสชันที่ปิดสนิท การดูแลจัดการเซสชัน Exchange มีแยกจำหน่ายผ่าน `remote-cli sessions` และ `remote-cli session-kill` การดึงข้อมูลเมตาคำสั่งที่ปักหมุดไม่อนุญาตให้ดำเนินการ: อุปกรณ์จะต้องยอมรับสิทธิ์การจัดการของผู้ปฏิบัติงานที่ปิดผนึก

## ไดเร็กทอรีคีย์เซสชันที่ปิดผนึกและการถ่ายทอดสิทธิ์

Exchange จัดเก็บคีย์สาธารณะของผู้ปฏิบัติงานและโทเค็นการให้สิทธิ์ `rsg1.` ที่เจ้าของลงนาม เพื่อให้ลูกค้าสามารถค้นพบกันและกันได้ **นี่ไม่ใช่การอนุญาต อุปกรณ์ ** ยังคงตรวจสอบลายเซ็นของเจ้าของกับนโยบายท้องถิ่น Exchange จัดทำดัชนีเพย์โหลดโทเค็นเท่านั้น (`owner_key`, `operator_key`, `device_id`, `level`, `expires_at`) และจะไม่ถือว่าการอัปโหลดเป็นการอนุญาต Exchange ไม่สามารถปลอมแปลงลายเซ็นรูทได้ แต่สามารถระงับข้อมูลประจำตัว นำเสนอบัญชีรายชื่ออย่างไม่ถูกต้องไปยังไคลเอนต์รูทที่อนุมัติอัตโนมัติ สังเกตข้อมูลเมตา หรือปฏิเสธบริการ

รูทของเวิร์กสเปซ (`rsr1.`) และคำเชิญให้สิทธิ์ (`rsi1.`) เป็นการถ่ายทอดประเภทเดียวกัน Exchange ตรวจสอบรูปร่างโทเค็นและลายเซ็นตนเองของ Ed25519 (คำเชิญจะต้องตรงกับรูทคีย์ที่เก็บไว้ด้วย) และ **not** จะตัดสินใจว่าคีย์ตัวดำเนินการใดที่ได้รับการอนุมัติ `GET /api/seal/roster` เป็นมุมมองผู้ดูแลระบบระดับสูงของสมาชิก บทบาท และ `operator_device_grants` ลูกค้าจะต้องไม่ถือว่าคีย์ที่ปรากฏใหม่นั้นเชื่อถือได้จนกว่าไคลเอนต์รูทจะอนุมัติคีย์นั้นในเครื่อง ดูที่ `docs/sealed-sessions.md`

สมาชิกพื้นที่ทำงานที่ได้รับการตรวจสอบสิทธิ์สามารถแสดงรายการคีย์ ลงทะเบียนคีย์ของตนเอง และอัปโหลดสิทธิ์ (สิทธิ์คือการตรวจสอบสิทธิ์ด้วยตนเอง) `DELETE` ของคีย์นั้นจำกัดอยู่ที่บัญชีที่ลงทะเบียน (ผู้ดูแลระบบอาจลบออกก็ได้) `DELETE` ของทุนจำกัดเฉพาะผู้อัปโหลด (ผู้ดูแลระบบอาจลบรายการใดก็ได้) `GET /api/seal/grants` ข้ามแถวที่หมดอายุ

หลักฐานการลงทะเบียนที่สำคัญคือ Ed25519 มากกว่า:

```text
remote-seal-key-register:v1:<workspace_id>:<operator_id>:<public_key>:<issued_at_ms>
```

`issued_at_ms` ต้องอยู่ภายใน ±10 นาทีของนาฬิกาเซิร์ฟเวอร์ `public_key` และ `proof` เป็น base64 มาตรฐาน (คีย์ 32 ไบต์, ลายเซ็น 64 ไบต์)

| วิธีการและเส้นทาง | ร่างกาย | ความสำเร็จ |
| --- | --- | --- |
| `POST /api/seal/keys` | `{"public_key","label"?,"proof","issued_at_ms"}` | รหัสที่ลงทะเบียน (upsert สำหรับบัญชีเดียวกัน) |
| `GET /api/seal/keys` | — | `[{operator_id, username, email, public_key, label, created_at, last_seen_at}]` |
| `DELETE /api/seal/keys/{key}` | — | `204` (`{key}` เป็นคีย์สาธารณะที่เข้ารหัส URL) |
| `POST /api/seal/grants` | `{"tokens":["rsg1.…"]}` | `{stored:[…], errors:[…]}` |
| `GET /api/seal/grants?mine=1` | — | มอบสิทธิ์ให้ `operator_key` เป็นหนึ่งในคีย์ที่ลงทะเบียนของผู้โทร เงินช่วยเหลือแต่ละรายการที่ `owner_key` ไม่ใช่รูทที่เผยแพร่ยังรวมถึง `chain` ด้วย (โทเค็นใบรับรอง อาจว่างเปล่า) |
| `GET /api/seal/grants?owner_key=…` | — | ทุนที่ลงนามโดยรหัสเจ้าของนั้น |
| `DELETE /api/seal/grants/{id}` | — | `204` |
| `PUT /api/seal/root` | `{"token":"rsr1.…","rotation":"rsx1.…"?}` | รูทพื้นที่ทำงานที่เก็บไว้ ผู้ดูแลระบบระดับสูงเท่านั้น มีการตรวจสอบรูปร่างและลายเซ็นของคีย์เอง นี่ไม่ใช่การอนุมัติ `rotation` (ทางเลือก) ต้องลงนามโดย root ที่เผยแพร่ในปัจจุบัน และตั้งชื่อ root ใหม่ จากนั้น Exchange จะพุช `seal_root {token, rotation}` ไปยังอุปกรณ์ที่ปิดผนึกแบบออนไลน์ ซึ่งจะปักหมุดใหม่เมื่อมีการลงนามการหมุนโดยรูทที่พวกเขาเชื่อถืออยู่แล้วเท่านั้น หากไม่มีการหมุนที่ถูกต้อง อุปกรณ์ที่ปักหมุดรูทเก่าจะเก็บไว้ |
| `GET /api/seal/root` | — | `{workspace_id, root_key, payload, signature, token, created_at}` หรือ `404` เมื่อไม่มีการเผยแพร่ |
| `POST /api/seal/invitations` | `{"token":"rsi1.…"}` | คำเชิญที่เก็บไว้ ผู้ดูแลระบบระดับสูงเท่านั้น และหลังจากที่รูทแล้วเท่านั้น |
| `GET /api/seal/invitations` | — | คำเชิญที่ยังไม่หมดอายุ ใหม่ล่าสุดมาก่อน (`[]` เมื่อไม่มี) |
| `GET /api/seal/roster` | — | `{members:[{operator_id, username, email, role, restricted, enabled, keys:[{public_key, label}], device_grants:[{device_id, can_manage}]}]}`. ผู้ดูแลระบบระดับสูงเท่านั้น รวมถึงสมาชิกผู้พิการด้วย |

ความไม่พร้อมใช้งานของไดเร็กทอรีไม่ได้ข้ามข้อกำหนดเซสชันที่ปิดผนึกหรือการให้สิทธิ์อุปกรณ์ `GET /api/seal/root` ส่งคืน `404` เมื่อไม่มีการเผยแพร่เวิร์กสเปซ root

```bash
# Register this operator key (proof signed locally)
curl -sS -X POST "$BASE/api/seal/keys" \
  -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \
  -d '{"public_key":"<ed25519-b64>","label":"cli","proof":"<sig-b64>","issued_at_ms":1700000000000}'

curl -sS "$BASE/api/seal/keys" -H "Authorization: Bearer ${TOKEN}"

curl -sS -X POST "$BASE/api/seal/grants" \
  -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \
  -d '{"tokens":["rsg1.…"]}'

curl -sS "$BASE/api/seal/grants?mine=1" -H "Authorization: Bearer ${TOKEN}"
```

### ใบรับรองระดับกลาง (`rsc1.`)

Exchange เก็บใบรับรองระดับกลางเพื่อให้คีย์ที่ไม่ใช่รูทสามารถลงนามสิทธิ์ที่อุปกรณ์จะยอมรับได้ ** ตรวจสอบ ** ลายเซ็น Ed25519 ของผู้ออก และ ** ไม่สามารถปลอมแปลงใบรับรอง ** ได้ การอัปโหลดโทเค็นไม่ได้รับการอนุญาต: ผู้เรียกจะต้องเป็นผู้ดำเนินการของผู้ออก ใบรับรองที่ลงนามระดับรูท (`issuer_key` เท่ากับรูทของเวิร์กสเปซที่เผยแพร่) อาจเผยแพร่ได้โดยผู้ดูแลระบบระดับสูงของเวิร์กสเปซเท่านั้น ตัวกลางอาจถูกเผยแพร่โดยบัญชีที่มีรหัสประทับตราที่ลงทะเบียนคือ `issuer_key` เท่านั้น และผู้ออกนั้นจะต้องอยู่ภายใต้ใบรับรองที่เก็บไว้ ไม่ถูกเพิกถอน และยังไม่หมดอายุพร้อมกับ `can_issue` เด็กจะต้องอยู่ในระดับผู้ปกครอง ขอบเขตอุปกรณ์ และการหมดอายุ (ใช้การตรวจสอบอุปกรณ์เดียวกัน) การเผยแพร่ `(issuer_key, subject_key)` เดิมอีกครั้งจะแทนที่แถวเก่าและล้างการเพิกถอน

`GET /api/seal/certs/chain?subject_key=` ส่งคืนลิงก์ผู้ออกการเดินของ `{chain:[tokens]}` จนถึงรูทที่เผยแพร่ ใบรับรองสูงสุด 4 ใบ ออกให้รูทก่อน หรือ `404` สมาชิกที่ได้รับการรับรองความถูกต้องอาจเรียกมันว่า `GET /api/seal/grants?mine=1` เพิ่ม `chain` บนสิทธิ์ที่ผู้ลงนามไม่ใช่รูท ดังนั้นไคลเอ็นต์จะได้รับพร้อมกับสิทธิ์ เงินช่วยเหลือที่ลงนามโดยรูทจะละเว้น `chain`

`GET /api/seal/certs` แสดงรายการทุกแถว รวมถึงแถวที่ถูกเพิกถอน (ผู้ดูแลระบบและผู้ดูแลระบบระดับสูง) `DELETE /api/seal/certs/{serial}` (บัญชีของผู้ออกหรือผู้ดูแลระบบระดับสูง) ตั้งค่า `revoked` ที่หยุดการกระจายเท่านั้น อุปกรณ์จะปฏิบัติตามสำเนาที่พวกเขามีอยู่แล้วจนกว่าจะหมดอายุ การเพิกถอนแบบถาวรคือ `revoked_keys` ของนโยบายอุปกรณ์ `{serial}` เป็นอนุกรม base64 มาตรฐานที่เข้ารหัส URL

| วิธีการและเส้นทาง | ร่างกาย | ความสำเร็จ |
| --- | --- | --- |
| `POST /api/seal/certs` | `{"token":"rsc1.…"}` | ใบรับรองที่เก็บไว้ (เพิ่มโดยผู้ออกและหัวเรื่อง) |
| `GET /api/seal/certs/chain?subject_key=` | — | `{chain:["rsc1.…"]}`, ออก root ก่อน หรือ `404` |
| `GET /api/seal/certs` | — | `[{serial, issuer_key, subject_key, token, expires_at_ms, created_at, revoked}]`. ผู้ดูแลระบบหรือผู้ดูแลระบบขั้นสูง |
| `DELETE /api/seal/certs/{serial}` | — | `204`. ผู้ออกหรือผู้ดูแลระบบขั้นสูง หยุดการกระจายเท่านั้น |

```bash
curl -sS -X POST "$BASE/api/seal/certs" \
  -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \
  -d '{"token":"rsc1.…"}'

curl -sS "$BASE/api/seal/certs/chain?subject_key=<ed25519-b64>" \
  -H "Authorization: Bearer ${TOKEN}"
```

### การสำรองข้อมูลคีย์รูทของพื้นที่ทำงาน (ไม่บังคับ)

`PUT` / `GET` / `DELETE /api/seal/backup` เก็บ `rsb1.` ทึบแสงหนึ่งหยดต่อบัญชี `PUT` และ `GET` เป็นผู้ดูแลระบบระดับสูงเท่านั้น: blob คือคีย์ส่วนตัวของพื้นที่ทำงาน **root** ซึ่งเข้ารหัสบนไคลเอ็นต์ด้วย Argon2id (64 MiB, t=3, p=1) และ ChaCha20-Poly1305 ข้อความรหัสผ่านเป็นทางเลือกและไม่มีความเข้มงวดในการบังคับใช้ ** ด้วยข้อความรหัสผ่านที่ว่างเปล่าหรืออ่อนแอ ใครก็ตามที่ถือ Blob (รวมถึง Exchange) จะสามารถถอดรหัสคีย์รูทและควบคุมทุกอุปกรณ์ได้ ** การอัปโหลดเป็นทางเลือก ลูกค้าสามารถส่งออกคีย์รูท (`rsk1.`) และเก็บไว้แบบออฟไลน์แทน Exchange ตรวจสอบว่า `blob` เริ่มต้นด้วย `rsb1.` และความยาวของมัน และไม่เคยถอดรหัส `GET` จำกัดอยู่ที่ 5 คำขอต่อบัญชีต่อชั่วโมงต่อกระบวนการ (HTTP 429) การอ่าน เขียน และการลบจะถูกตรวจสอบด้วยจำนวนไบต์เท่านั้น การตอบกลับส่ง `Cache-Control: no-store`

| วิธีการและเส้นทาง | ร่างกาย | ความสำเร็จ |
| --- | --- | --- |
| `PUT /api/seal/backup` | `{"blob":"rsb1.…"}` | `{blob, updated_at}` |
| `GET /api/seal/backup` | — | `{blob, updated_at}` หรือ `404` เมื่อไม่มีการจัดเก็บ อัตราจำกัด |
| `DELETE /api/seal/backup` | — | `204` รวมถึงเมื่อไม่มีแถวอยู่ |

```bash
curl -sS -X PUT "$BASE/api/seal/backup" \
  -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \
  -d '{"blob":"rsb1.…"}'

curl -sS "$BASE/api/seal/backup" -H "Authorization: Bearer ${TOKEN}"

curl -sS -X DELETE "$BASE/api/seal/backup" -H "Authorization: Bearer ${TOKEN}"
```

## การตรวจสอบสิทธิ์รูทเชนส่วนขยายของเบราว์เซอร์

การทำงานของเบราว์เซอร์ใช้เซสชัน WebSocket ที่ปิดผนึกแบบเนทีฟ ไม่ใช่ HTTP one-shot
การดำเนินการ ส่วนขยายจะสร้างคีย์ตัวดำเนินการ Ed25519 ด้วยตนเอง โดยมีผู้ปฏิบัติงาน
โทเค็นผู้ถือจะได้รับ `tenant_id` / `operator_id` จาก `/api/auth/me` จากนั้นจึงโทร
`POST /api/seal/keys` พร้อม `{public_key, label, proof, issued_at_ms}` สัญญาณ PoP:

```text
remote-seal-key-register:v1:<workspace_id>:<operator_id>:<public_key>:<issued_at_ms>
```

คีย์/ลายเซ็นเป็น base64 มาตรฐาน (32/64 ไบต์) เวลาจะต้องอยู่ภายใน ±10
นาทีของนาฬิกาเซิร์ฟเวอร์ การจดทะเบียนพิสูจน์การครอบครอง ไม่ใช่การอนุญาต
ส่วนขยายจะซิงค์ `GET /api/seal/root`, `GET /api/seal/grants?mine=1` และ
โทเค็นใบรับรองที่รวมอยู่ในทุนหรือดึงมาจาก
`GET /api/seal/certs/chain?subject_key=<url-encoded-key>`. เงินช่วยเหลือจะต้องตรวจสอบเพื่อ
รูทพื้นที่ทำงานที่เชื่อถือได้ โดยตรงหรือผ่านสายใบรับรองที่ถูกต้องด้วย
ขอบเขต ระดับ และความถูกต้องของอุปกรณ์ที่ถูกต้อง การลงทะเบียนโทเค็นอุปกรณ์ยังคงอยู่
แยกจากโฟลว์ไดเรกทอรีโอเปอเรเตอร์โทเค็นนี้

ส่วนขยายจะตรวจสอบ Hellos ดั้งเดิมที่เข้ามาผ่านทางรูทและรวมส่วนขยายด้วย
เป็นเจ้าของ Hello/Grant/Chain ที่ลงนามใน `SealAccept.peer` CLI/console ตรวจสอบหลักฐานนั้น
และต้องมี `peer.operator_key == accept.device_key` ก่อนยอมรับการลงนาม
การตอบสนอง เอเจนต์ Rust ใช้นโยบายรูทปกติเมื่อส่วนขยายทำหน้าที่เป็น
ผู้ปฏิบัติงาน การพิสูจน์ที่ไม่ถูกต้อง/ขาดหายไปล้มเหลวในการปิด ไม่มีเบราว์เซอร์แยกต่างหาก
trust store/UI หรือทางเลือกดั้งเดิม อุปกรณ์ดั้งเดิมและพื้นที่ทำงาน-TOFU รูทยังคงอยู่
ส่วนขยายจะจัดเก็บรูทแรกต่อเซิร์ฟเวอร์/พื้นที่ทำงาน และปฏิเสธการเปลี่ยนแปลง

**Security:** ตรวจสอบรูทแรกนอกแบนด์ PoP หรือบทบาทของเซิร์ฟเวอร์ไม่ได้
การอนุมัติรูต ตรวจสอบคีย์ที่ไม่รู้จักก่อนการอนุมัติอัตโนมัติของไคลเอ็นต์รูท ป้องกัน
โปรไฟล์/คีย์ปลายทางและสคริปต์ที่โฮสต์ การลบไดเรกทอรีไม่เพิกถอน
ข้อมูลประจำตัวที่ลงนามในแคช Exchange สามารถดูข้อมูลเมตา ระงับข้อมูลรับรอง หรือ
ปฏิเสธการให้บริการ การปิดผนึกไม่ได้แยกตัวดำเนินการ CDP เต็มรูปแบบที่ไม่เป็นมิตรออกจากกัน

## การเลือก WebSocket หรือ HTTPS

| ต้องการ | ใช้ |
| --- | --- |
| คำสั่ง, การตรวจสอบหรือยกเลิกงานที่ค้างไว้, เซ็นเซอร์, โพรบเครือข่าย, อัปเดต, ubus, คำสั่งที่ปักหมุด | เซสชัน WebSocket ที่ปิดผนึกแบบเนทีฟ |
| PTY แบบโต้ตอบ, เอาต์พุตสตรีมมิ่ง, การถ่ายโอนไฟล์, หน้าจอ Android/ทรี/แตะ | เซสชัน WebSocket ที่ปิดผนึกแบบเนทีฟ |
| การตรวจสอบสิทธิ์ คลังอุปกรณ์ ไดเร็กทอรีคีย์/สิทธิ์/ใบรับรอง การสำรองข้อมูลรูท | HTTPS API |
| สินค้าคงคลังเซสชันการกำหนดเส้นทาง Exchange และการยกเลิก | จุดสิ้นสุดการดูแลเซสชัน HTTPS |
| การมีอยู่ของอุปกรณ์ที่ใช้งานอยู่และการเชื่อมต่อของผู้ให้บริการที่มีอายุการใช้งานยาวนาน | WebSocket; การทำงานของอุปกรณ์ในการเชื่อมต่อยังคงต้องมีการปิดผนึก |

การไหลของตั๋วเบราว์เซอร์ (ข้อมูลประจำตัวไม่เคยอยู่ใน URL ของ WebSocket):

1. `POST /api/ws-ticket` พร้อม `Authorization: Bearer …`
2. เชื่อมต่อกับ `GET /ws/browser` และส่งเฟรม JSON แรก `{"ticket":"…"}`
3. สร้างการจับมือไคลเอ็นต์/อุปกรณ์แบบปิดผนึกดั้งเดิมก่อนส่งการดำเนินการใดๆ ของอุปกรณ์ การให้สิทธิ์และห่วงโซ่ใบรับรองของผู้ปฏิบัติงานจะต้องตรวจสอบกับรูทที่อุปกรณ์ไว้วางใจ

ไคลเอ็นต์แบบเนทีฟใช้ `GET /ws/operator` พร้อมการรับรองความถูกต้องของผู้ปฏิบัติงานและข้อกำหนดเซสชันที่ปิดผนึกเหมือนกัน การเชื่อมต่อ WebSocket หรือการเข้าสู่ระบบบัญชีที่สำเร็จเพียงอย่างเดียวไม่ได้ให้อนุญาตการทำงานของอุปกรณ์ โปรดดูที่ [Web console และ Web Push](/docs/th/web-console-and-notifications.md), [protocol](/docs/th/websocket-protocol.md) และ [sealed sessions](/docs/th/sealed-sessions.md)

## เอกสารสรุปสถานะข้อผิดพลาด

| สถานะ | สาเหตุทั่วไป |
| --- | --- |
| `400` | อินพุตการดำเนินการไม่ถูกต้อง เช่น คำสั่งว่างหรือ URL แหล่งที่มาของการอัปเดตไม่ถูกต้อง การตรวจสอบความถูกต้องเกิดขึ้นก่อนการปฏิเสธเซสชันที่ปิดผนึก |
| `401` | โทเค็น Bearer หายไปหรือไม่ถูกต้อง |
| `403` | การอนุญาตบัญชีหรืออุปกรณ์ไม่อนุญาตให้ทำการร้องขอ |
| `409` | การดำเนินการของอุปกรณ์ HTTP ที่ถูกต้องและได้รับอนุญาตต้องมีเซสชัน WebSocket ที่ปิดผนึก ไม่มีการจัดส่งอุปกรณ์เกิดขึ้น |
| `404` | รายการไดเรกทอรีหายไปหรือรูทพื้นที่ทำงานที่ไม่ได้เผยแพร่ ตามสัญญาปลายทาง |
| `429` | ขีดจำกัดอัตราเฉพาะปลายทาง รวมถึงการอ่านข้อมูลสำรองระดับรูทของพื้นที่ทำงาน |
| `503` | สินค้าคงคลังของเซสชันไม่พบอุปกรณ์ในสถานะฮับ Exchange ภายในเครื่อง |

เส้นทาง HTTP การดำเนินการของอุปกรณ์ไม่รอเอาต์พุต agent ดังนั้นผลลัพธ์ที่ได้รับอนุญาตตามปกติคือ `409` ไม่ใช่ความสำเร็จในการดำเนินการ ข้อผิดพลาดในการจัดส่งแบบออฟไลน์ หรือการหมดเวลาการดำเนินการ HTTP ข้อผิดพลาดจากข้อมูลเมตา การตรวจสอบสิทธิ์ และ API ไดเรกทอรีจะคงความหมายเฉพาะตำแหน่งข้อมูลไว้

## แผนการตรวจสอบความถูกต้องด้วยตนเอง

ใช้บัญชีทดสอบและอุปกรณ์ที่มีสิทธิ์ที่ทราบ การตรวจสอบการปฏิเสธ HTTP ไม่จำเป็นต้องดำเนินการบนอุปกรณ์:

1. ตรวจสอบและแสดงรายการอุปกรณ์ผ่าน HTTPS ตรวจสอบสิทธิ์บัญชีและอุปกรณ์
2. ส่งคำขอที่ถูกต้องไปยังแต่ละเส้นทางการทำงานของอุปกรณ์ในตารางการปฏิเสธ คาดหวัง HTTP `409` และยืนยันว่าไม่มีการดำเนินการใดๆ ไปที่ agent รวมถึงการอ่านงานที่ค้างไว้และการรันคำสั่งที่ปักหมุดไว้
3. ตรวจสอบข้อมูลรับรองที่ไม่ถูกต้อง บัญชีต้องห้าม และคำขอที่ป้อนไม่ถูกต้องแยกกัน คาดว่าจะมีข้อผิดพลาดในการรับรองความถูกต้อง การอนุญาต และการตรวจสอบ แทนที่จะถือว่าทุกคำขอส่งคืน `409`
4. อ่านรายการเซสชัน Exchange สำหรับเซสชันแบบใช้แล้วทิ้งที่เลือกไว้อย่างชัดเจน ให้ทดสอบการยกเลิกและตรวจสอบ `released` เซสชันที่หายไปส่งคืน `released:false` รายการเซสชันบนอุปกรณ์ออฟไลน์ในเครื่องส่งคืน `503`
5. ดำเนินการอนุมัติรูทและให้สิทธิ์ จากนั้นรันการดำเนินการ CLI ดั้งเดิมที่ไม่เป็นอันตราย เช่น `remote-cli exec "$DEVICE_ID" -- uname -a` ตรวจสอบว่าทำได้สำเร็จผ่านการจับมือที่ปิดสนิท การให้สิทธิ์อุปกรณ์ที่สูญหายหรือไม่ถูกต้องจะต้องปิดไม่สำเร็จ
6. ตรวจสอบความถูกต้องของการลงทะเบียนคีย์ การอนุญาต/การดึงใบรับรอง การสำรองข้อมูลรูทเสริม และการตรวจสอบสิทธิ์รูทเชนของเบราว์เซอร์อย่างอิสระ HTTPS API เหล่านั้นยังคงใช้งานได้ภายใต้สัญญาข้างต้น

## ปลายทางการส่งออกบันทึกการตรวจสอบ

รองรับเฉพาะ PostgreSQL Exchange และจำกัดอยู่ในพื้นที่ทำงานที่ผ่านการรับรองความถูกต้อง เฉพาะผู้ดูแลระบบและ superadmin เท่านั้น บัญชีอื่นจะได้รับ 403 ไม่รองรับ exchange-worker และไม่มีหน้าตั้งค่าใน Web/Mobile Console

```text
GET    /api/audit/sinks
POST   /api/audit/sinks
PATCH  /api/audit/sinks/{id}
DELETE /api/audit/sinks/{id}
POST   /api/audit/sinks/{id}/test
GET    /api/audit/sinks/status
```

```sh
printf '%s' '{"name":"archive","kind":"webhook","endpoint":"https://audit.example.net/events","secret":"REPLACE-WITH-RANDOM-SECRET","mode":"full"}' | remote-cli audit sink add
remote-cli audit sink list
printf '%s' '{"interval_ms":500,"batch_size":16}' | remote-cli audit sink update 1
remote-cli audit sink test 1
remote-cli audit sink status
remote-cli audit sink remove 1
```

โหมด throttled ซึ่งเป็นค่าเริ่มต้นจะส่งออกบันทึกการตรวจสอบที่จัดเก็บไว้ ข้อความที่ยาวกว่า 256 KiB จะเก็บ 250 KiB แรกและ 6 KiB สุดท้าย ไฟล์จะแสดงเป็นแฮช และสตรีมจะส่งออกเมื่อเซสชันสิ้นสุด โหมด full เก็บข้อมูลก่อนตัดทอน โดยแบ่ง JSON ต้นฉบับและเนื้อหาไบนารีเป็นชิ้น (ค่าเริ่มต้น 1 MiB) พร้อม ID การทำงาน/สินทรัพย์ ลำดับ ออฟเซ็ต เครื่องหมายชิ้นสุดท้าย SHA-256 และขนาดรวม สำหรับเซสชันที่ปิดผนึก ทั้งสองโหมดส่งออกเฉพาะข้อมูลเมตาและการอ้างอิง/แฮชของข้อมูลเข้ารหัส ไม่มีโหมดใดกู้คืนข้อความธรรมดาบนปลายทางได้

ข้อมูลลับและส่วนหัวถูกเข้ารหัสและอ่านกลับเป็น *** คำขอ PATCH จะคงค่าเดิมเมื่อรับ *** ส่วนส่วนหัวที่มีค่า null จะถูกลบ การส่งใช้คิว FIFO แบบคงทนที่ทำงานเบื้องหลังและลองใหม่โดยเพิ่มระยะรอแบบเอ็กซ์โพเนนเชียล เมื่อคิว throttled เต็ม จะทิ้งรายการเก่าสุดและนับจำนวนที่ทิ้ง ส่วนคิว full บนดิสก์จะชะลอผู้ส่งเมื่อเต็ม สถานะแสดงความสำเร็จ/ล้มเหลว งานค้าง รายการที่ทิ้ง และช่องว่าง การลบปลายทางจะทิ้งข้อมูลที่รอส่งและบันทึกจำนวนไว้ การเปลี่ยนการตั้งค่าถูกบันทึกการตรวจสอบในธุรกรรมเดียวกัน

Webhook ส่งอาร์เรย์ events ใน JSON โดยเข้ารหัสไบต์เป็น base64 ตรวจสอบ X-Remote-Timestamp และ X-Remote-Signature: sha256=<hex> ด้วย HMAC-SHA256(timestamp + "." + raw body) OTel ใช้ OTLP/HTTP JSON /v1/logs, body.bytesValue สำหรับชิ้นข้อมูล, แอตทริบิวต์ remote.device.id / remote.operator / remote.action และ service.name ที่กำหนดค่าได้ gRPC ต้องใช้ Collector เป็นตัวกลาง ไม่มีตัวส่งออก gRPC โดยตรง

ด้านล่างคือตัวอย่างการตั้งค่าเริ่มต้น payload ของ webhook การตรวจสอบลายเซ็น และ Collector ต้องตรวจสอบเนื้อหา HTTP ดิบ จัดเก็บและตัดรายการซ้ำด้วย delivery_id ก่อนตอบ 2xx เปิด allow_structured_metadata ใน Loki 3.x เพิ่มแหล่งข้อมูล Loki ใน Grafana แล้วค้นหา {service_name="remote-exchange"} หากใช้ gRPC ให้เปลี่ยน exporters ของ pipeline เป็น [otlp/grpc] ใช้ TLS และการรับรองความถูกต้องในระบบจริง

```json
{"name":"collector","kind":"otel","mode":"throttled","endpoint":"https://collector.example.net:4318/v1/logs","headers":{"Authorization":"Bearer REPLACE"},"service_name":"remote-exchange","resource":{"deployment.environment.name":"production"},"batch_size":32,"interval_ms":1000,"chunk_bytes":1048576,"queue_limit":10000,"overflow_bytes":10737418240,"actions":[],"devices":[],"operators":[],"enabled":true,"ca_pem":null}
```

```json
{"version":1,"events":[{"delivery_id":123,"type":"full","mode":"full","entry":{"id":42,"action":"exec","operator_name":"alice","device_id":"dev"},"data":{"kind":"binary","asset_id":"asset-uuid","path":"/data_base64","sequence":0,"offset":0,"last":true,"data_base64":"YWJj","total_size":3,"sha256":"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad","context":{"direction":"output","frame_sequence":0,"stream_offset":0}}}]}
```

```python
import hashlib, hmac, time

def verify(headers, raw_body, secret):
    stamp = headers['X-Remote-Timestamp']
    if abs(time.time() - int(stamp)) > 300:
        raise ValueError('expired webhook')
    expected = 'sha256=' + hmac.new(
        secret.encode(), stamp.encode() + b'.' + raw_body, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(expected, headers['X-Remote-Signature']):
        raise ValueError('invalid signature')
```

```yaml
receivers:
  otlp:
    protocols:
      http:
        endpoint: 127.0.0.1:4318
processors:
  batch: {}
exporters:
  otlphttp/loki:
    endpoint: http://loki:3100/otlp
  otlp/grpc:
    endpoint: telemetry.example.net:4317
    headers:
      Authorization: "${env:OTEL_AUTHORIZATION}"
    tls:
      insecure: false
service:
  pipelines:
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp/loki]
```

| ฟิลด์การตรวจสอบ | ฟิลด์ OTLP |
|---|---|
| service_name / resource | Resource service.name / attributes |
| entry.started_at | timeUnixNano |
| entry.id / delivery_id | remote.audit.id / remote.delivery.id |
| entry.device_id / operator_name / action | remote.device.id / remote.operator / remote.action |
| entry.tenant_id | remote.workspace.id |
| entry.started_at / ended_at / detail | remote.started_at / remote.ended_at / remote.result |
| traceId / spanId | traceId / spanId |
| data.data_base64 | body.bytesValue / remote.chunk.* |
| เหตุการณ์ที่จัดเก็บ | body.stringValue |
