Tác nhân AI nên tìm nạp https://remote.ivjn.us/SKILL.md.

# API HTTP cho hoạt động Agent

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

Hoạt động niêm phong gốc hiện hỗ trợ người gọi chỉ HTTP. Sử dụng `remote-cli --transport http` hoặc `REMOTE_TRANSPORT=http`. `POST /api/sealed/connections` tạo hộp thư; `POST /input` gửi các bản bắt tay hoặc văn bản mã hóa đã ký, kết quả thăm dò `GET /events` và `DELETE` đóng kết nối. Khách hàng giữ lại mật mã đầu cuối và ủy quyền thiết bị hiện có. Điểm cuối hành động của thiết bị ban đầu vẫn trả về 409. Kết nối thiết bị không thay đổi. Hộp thư HTTP yêu cầu có mối quan hệ giống với phiên bản Exchange đang tạo và hết hạn sau 90 giây mà không hoạt động. Tuần tự hóa các đầu vào, chỉ thử lại cùng một chuỗi và văn bản mã hóa, đồng thời xác nhận các sự kiện đã xử lý bằng `after`. Web Console, thiết bị di động và mạng LAN vẫn giữ nguyên phương tiện truyền tải hiện có của chúng. Việc thực thi cài sẵn vẫn yêu cầu `manage`.

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


HTTP `409` / `Sealed sessions are required.` tại điểm cuối thao tác thiết bị có nghĩa là cần phiên kín; chỉ mã thông báo Bearer không thể thực thi thao tác thiết bị. Dùng `remote-cli --profile alice exec lab -- uname -a`, hoặc `remote-cli --profile alice --transport http exec lab -- uname -a` trong môi trường chỉ cho phép HTTP. CLI vẫn thực hiện bắt tay có chữ ký và mã hóa đầu cuối.

## URL cơ sở

Sản xuất Exchange:

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

Triển khai tự lưu trữ sử dụng nguồn gốc HTTPS của riêng bạn. Tất cả các đường dẫn bên dưới đều liên quan đến cơ sở đó. Gửi JSON bằng `Content-Type: application/json`.

## Xác thực

Mỗi tuyến đường `/api/*` (ngoại trừ người trợ giúp xác thực/đăng ký công khai) yêu cầu:

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

`<token>` có thể là:

1. **Mã thông báo phiên đăng nhập mật khẩu** từ `POST /api/auth/login`
2. Khóa API chính **Operator** (mã thông báo tồn tại lâu dài được hiển thị một lần khi tài khoản nhà điều hành được tạo)

Cả hai đều được chấp nhận bởi cùng một đường dẫn `authenticate_operator` được bảng điều khiển và CLI sử dụng.

### Đăng nhập bằng mật khẩu

```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` là danh tính tài khoản và phải là địa chỉ hợp lệ. Dịch vụ bình thường hóa nó (cắt, viết thường) và yêu cầu địa chỉ được xác minh. Các hoạt động triển khai hiện tại vẫn có thể gửi `{ "username": "alice", "password": "..." }` cho đến khi các tài khoản đó chuyển sang email; giá trị chứa `@` trong `username` được coi là một email. Địa chỉ không hợp lệ sẽ bị từ chối trước khi kiểm tra mật khẩu.

Phản hồi ví dụ:

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

Sử dụng `token` làm thông tin xác thực Bearer. Gọi `POST /api/auth/logout` với cùng tiêu đề để thu hồi phiên đó. Khóa API của nhà điều hành chính không bị thu hồi khi đăng xuất.

### Khóa API của nhà điều hành (mã thông báo chính)

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

## Liệt kê các thiết bị

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

Mỗi mục bao gồm `id`, `name`, `online`, `enabled`, `info`, dấu thời gian và `active_sessions`. Kiểm kê thiết bị là siêu dữ liệu Exchange. Việc liệt kê một thiết bị không thiết lập phiên kín cần thiết để vận hành thiết bị đó.

## Từ chối HTTP hành động thiết bị và quản lý phiên

Tất cả các hoạt động của thiết bị đều sử dụng phiên WebSocket kín. Các tuyến HTTP bên dưới không thể thiết lập bắt tay máy khách/thiết bị và do đó trả về HTTP `409` cho các yêu cầu được ủy quyền, xác thực vượt qua quá trình xác thực yêu cầu. Việc từ chối xảy ra trước khi gửi thiết bị: không có lệnh, yêu cầu cảm biến, thăm dò mạng, cập nhật, cuộc gọi ubus, yêu cầu công việc được giữ lại hoặc lệnh đã ghim được gửi tới agent. Một thiết bị trực tuyến không thể thực hiện được các tuyến đường này.

Xác thực, quyền và xác thực đầu vào vẫn được áp dụng. Thông tin xác thực không hợp lệ trả về `401`; hoạt động bị cấm trả về `403`; các lệnh, URL hoặc nội dung yêu cầu không hợp lệ sẽ giữ lại các lỗi xác thực của chúng, bao gồm `400`. Phiên kín `409` không thay thế các lỗi xác thực hoặc ủy quyền trước đó. Việc kiểm tra quyền đối với các lệnh được ghim tuân theo chính sách lệnh được ghim; chuyển chúng vẫn không cho phép thực thi HTTP.

| Phương pháp và đường dẫn | Hành vi sau khi xác thực, ủy quyền và xác nhận |
| --- | --- |
| `POST /api/devices/{id}/exec` | `409`: thực thi bên trong phiên WebSocket kín |
| `GET /api/devices/{id}/exec/holds` | `409`: kiểm tra các công việc được giữ trong phiên WebSocket được niêm phong |
| `POST /api/devices/{id}/exec/holds/{job_id}/kill` | `409`: dừng các công việc được giữ trong phiên WebSocket đã được niêm phong |
| `GET /api/devices/{id}/exec/holds/{job_id}/cat` | `409`: đọc đầu ra công việc được giữ bên trong phiên WebSocket được niêm phong |
| `POST /api/devices/{id}/sensors` | `409`: thu thập các cảm biến bên trong phiên WebSocket kín |
| `POST /api/devices/{id}/net` | `409`: chạy các đầu dò mạng bên trong phiên WebSocket kín |
| `POST /api/devices/{id}/update` | `409`: kiểm tra hoặc áp dụng các bản cập nhật bên trong phiên WebSocket kín |
| `POST /api/devices/{id}/ubus` | `409`: gọi ubus trong phiên WebSocket kín |
| `POST /api/devices/{id}/commands/{cmd}/run` | `409`: tìm nạp lệnh đã ghim và thực thi lệnh đó bên trong phiên WebSocket được niêm phong với quyền quản lý được ủy quyền của thiết bị |

Ví dụ: yêu cầu hành động hợp lệ này là kiểm tra từ chối, không phải giao diện thực thi lệnh:

```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}'
```

Yêu cầu HTTP `409` có thông báo lỗi tiếng Anh `Sealed sessions are required.` (được bản địa hóa theo ngôn ngữ yêu cầu). Thiết lập phiên WebSocket được niêm phong riêng để thực hiện thao tác. Không có phản hồi đầu ra lệnh thành công nào từ tuyến HTTP này.

### Quản trị phiên Exchange

Kiểm kê và hủy phiên vẫn có sẵn qua HTTP vì chúng quản lý siêu dữ liệu định tuyến Exchange. Chúng không thực thi các thao tác của thiết bị hoặc mở phiên thiết bị bổ sung.

| Phương pháp và đường dẫn | Cơ thể thành công |
| --- | --- |
| `GET /api/devices/{id}/sessions` | `{sessions:[DeviceSessionInfo], limit, count}` |
| `POST /api/devices/{id}/sessions/{session_id}/kill` | `{session_id, released}` |

Các trường `DeviceSessionInfo` là `session_id` (ID số phía thiết bị), `operator_session_id`, `kind`, `hold`, `age_secs` và `deadline_unix_ms`. `limit` là `max_sessions_per_device` (mặc định là 32); `count` là độ dài danh sách hiện tại. Danh sách đọc trực tiếp trạng thái trung tâm Exchange cục bộ; một thiết bị ngoại tuyến cục bộ trả về `503`. Kill sử dụng ID **Z** của thiết bị, gửi lệnh hủy thích hợp (bao gồm cả đối với lệnh thực thi bị giữ) và giải phóng tuyến cục bộ ngay cả khi thiết bị không trả lời. Phiên bị thiếu hoặc thiết bị ngoại tuyến cục bộ trả về HTTP `200` với `released:false`. Toán tử chỉ đọc nhận `403` cho cả hai điểm cuối.

```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}"
```

Trước khi từ chối giới hạn phiên đầy đủ, Exchange lấy lại các tuyến của nhà điều hành đã chết và các tuyến trước thời hạn: thời gian chờ thực thi + 15 giây, truyền tệp 3600 giây + 15 giây, các loại khác là 24 giờ. Người thực thi được giữ trong thời hạn của nó sẽ tách ra khi nhà điều hành ngắt kết nối thay vì bị giết.

### Hoạt động CLI kín gốc

CLI thiết lập các phiên kín như một phần của hoạt động bình thường của thiết bị. Trước tiên hãy hoàn thành phê duyệt gốc không gian làm việc và ủy quyền thiết bị; xem [phiên kín](/docs/vi/sealed-sessions.md). Không có lựa chọn không tham gia bản rõ. Các lệnh thông thường không cần thêm cờ niêm phong:

```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"
```

Hoạt động công việc được tổ chức chạy trong các phiên kín; Quản trị phiên Exchange được cung cấp riêng biệt thông qua `remote-cli sessions` và `remote-cli session-kill`. Việc tìm nạp siêu dữ liệu lệnh được ghim không cho phép thực thi: thiết bị phải chấp nhận quyền quản lý của nhà điều hành được niêm phong.

## Thư mục khóa phiên kín và cấp chuyển tiếp

Exchange lưu trữ khóa công khai của nhà điều hành và mã thông báo cấp `rsg1.` do chủ sở hữu ký để khách hàng có thể khám phá lẫn nhau. **Đây không phải là ủy quyền.** Các thiết bị vẫn xác minh chữ ký của chủ sở hữu theo chính sách địa phương. Exchange chỉ lập chỉ mục tải trọng mã thông báo (`owner_key`, `operator_key`, `device_id`, `level`, `expires_at`) và không bao giờ coi việc tải lên là quyền. Exchange không thể giả mạo chữ ký gốc nhưng có thể giữ lại thông tin xác thực, trình bày sai danh sách cho máy khách gốc tự động phê duyệt, quan sát siêu dữ liệu hoặc từ chối dịch vụ.

Thư mục gốc của không gian làm việc (`rsr1.`) và lời mời cấp quyền (`rsi1.`) là cùng một loại chuyển tiếp. Exchange kiểm tra hình dạng mã thông báo và chữ ký tự Ed25519 (lời mời cũng phải khớp với khóa gốc được lưu trữ) và **not** quyết định khóa toán tử nào được phê duyệt. `GET /api/seal/roster` là chế độ xem siêu quản trị viên về các thành viên, vai trò và `operator_device_grants`. Khách hàng không được coi khóa mới xuất hiện là đáng tin cậy cho đến khi khách hàng gốc phê duyệt nó cục bộ. Xem `docs/sealed-sessions.md`.

Bất kỳ thành viên không gian làm việc được xác thực nào cũng có thể liệt kê các khóa, đăng ký khóa riêng của họ và tải lên các khoản cấp phép (các khoản cấp phép tự xác thực). `DELETE` của một khóa được giới hạn trong tài khoản đăng ký (quản trị viên có thể xóa bất kỳ tài khoản nào). `DELETE` của một khoản trợ cấp được giới hạn cho người tải lên (quản trị viên có thể xóa bất kỳ khoản trợ cấp nào). `GET /api/seal/grants` bỏ qua các hàng đã hết hạn.

Bằng chứng đăng ký chính là Ed25519 trên:

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

`issued_at_ms` phải cách đồng hồ máy chủ trong vòng ±10 phút. `public_key` và `proof` là base64 tiêu chuẩn (khóa 32 byte, chữ ký 64 byte).

| Phương pháp và đường dẫn | Thân hình | thành công |
| --- | --- | --- |
| `POST /api/seal/keys` | `{"public_key","label"?,"proof","issued_at_ms"}` | khóa đã đăng ký (upsert cho cùng một tài khoản) |
| `GET /api/seal/keys` | — | `[{operator_id, username, email, public_key, label, created_at, last_seen_at}]` |
| `DELETE /api/seal/keys/{key}` | — | `204` (`{key}` là khóa chung được mã hóa URL) |
| `POST /api/seal/grants` | `{"tokens":["rsg1.…"]}` | `{stored:[…], errors:[…]}` |
| `GET /api/seal/grants?mine=1` | — | cấp mà `operator_key` là một trong những khóa đã đăng ký của người gọi. Mỗi cấp có `owner_key` không phải là gốc được xuất bản cũng bao gồm `chain` (mã thông báo chứng chỉ, có thể trống) |
| `GET /api/seal/grants?owner_key=…` | — | cấp quyền được ký bởi khóa chủ sở hữu đó |
| `DELETE /api/seal/grants/{id}` | — | `204` |
| `PUT /api/seal/root` | `{"token":"rsr1.…","rotation":"rsx1.…"?}` | gốc không gian làm việc được lưu trữ. Chỉ có quản trị viên cấp cao. Hình dạng và chữ ký riêng của khóa được kiểm tra; đây không phải là sự chấp thuận `rotation` (tùy chọn) phải được ký bởi root hiện đã được xuất bản và đặt tên cho root mới; Exchange sau đó đẩy `seal_root {token, rotation}` đến các thiết bị kín trực tuyến, các thiết bị này chỉ được ghim lại khi vòng xoay được ký bởi người gốc mà họ đã tin tưởng. Nếu không có vòng xoay hợp lệ, các thiết bị đã ghim gốc cũ sẽ giữ nguyên |
| `GET /api/seal/root` | — | `{workspace_id, root_key, payload, signature, token, created_at}` hoặc `404` khi chưa được xuất bản |
| `POST /api/seal/invitations` | `{"token":"rsi1.…"}` | lời mời được lưu trữ Chỉ dành cho quản trị viên cấp cao và chỉ sau khi có root |
| `GET /api/seal/invitations` | — | lời mời chưa hết hạn, mới nhất trước (`[]` khi không có) |
| `GET /api/seal/roster` | — | `{members:[{operator_id, username, email, role, restricted, enabled, keys:[{public_key, label}], device_grants:[{device_id, can_manage}]}]}`. Chỉ có quản trị viên cấp cao. Bao gồm các thành viên khuyết tật |

Việc không có sẵn thư mục không bỏ qua yêu cầu về phiên kín hoặc cấp thiết bị. `GET /api/seal/root` trả về `404` khi không có gốc không gian làm việc nào được xuất bản.

```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}"
```

### Chứng chỉ trung gian (`rsc1.`)

Exchange lưu trữ chứng chỉ trung gian để khóa không phải gốc có thể ký các cấp phép mà thiết bị sẽ chấp nhận. Nó **xác minh** chữ ký Ed25519 của nhà phát hành và **không thể giả mạo chứng chỉ**. Tải mã thông báo lên không phải là ủy quyền: người gọi phải là nhà điều hành của tổ chức phát hành. Chứng chỉ có chữ ký gốc (`issuer_key` tương đương với gốc của không gian làm việc đã xuất bản) chỉ có thể được xuất bản bởi quản trị viên cấp cao của không gian làm việc. Một sản phẩm trung gian chỉ có thể được xuất bản bởi tài khoản có khóa con dấu đã đăng ký là `issuer_key` và bản thân nhà phát hành đó phải là đối tượng của chứng chỉ được lưu trữ, chưa bị thu hồi, chưa hết hạn với `can_issue`. Trẻ phải ở trong cấp độ, phạm vi thiết bị và thời hạn sử dụng của cha mẹ (áp dụng các thiết bị kiểm tra tương tự). Xuất bản lại `(issuer_key, subject_key)` tương tự sẽ thay thế hàng cũ hơn và xóa việc thu hồi.

`GET /api/seal/certs/chain?subject_key=` trả về `{chain:[tokens]}` nhà phát hành đi bộ liên kết đến thư mục gốc đã xuất bản, tối đa 4 chứng chỉ, chứng chỉ gốc được cấp trước hoặc `404`. Bất kỳ thành viên được xác thực nào cũng có thể gọi nó. `GET /api/seal/grants?mine=1` cho biết thêm rằng `chain` đối với các khoản trợ cấp mà người ký không phải là người gốc, vì vậy khách hàng sẽ nhận được nó cùng với khoản trợ cấp. Các khoản tài trợ được ký bởi người chủ bỏ qua `chain`.

`GET /api/seal/certs` liệt kê mọi hàng, bao gồm cả những hàng bị thu hồi (quản trị viên và quản trị viên cấp cao). `DELETE /api/seal/certs/{serial}` (tài khoản của nhà phát hành hoặc quản trị viên cấp cao) đặt `revoked`. Điều đó chỉ dừng phân phối. Các thiết bị tiếp tục tôn trọng bản sao mà chúng đã có cho đến khi nó hết hạn; thu hồi cứng là `revoked_keys` của chính sách thiết bị. `{serial}` là chuỗi nối tiếp base64 tiêu chuẩn được mã hóa URL.

| Phương pháp và đường dẫn | Thân hình | thành công |
| --- | --- | --- |
| `POST /api/seal/certs` | `{"token":"rsc1.…"}` | chứng chỉ được lưu trữ (được cấp bởi nhà phát hành và chủ đề) |
| `GET /api/seal/certs/chain?subject_key=` | — | `{chain:["rsc1.…"]}`, được cấp root trước hoặc `404` |
| `GET /api/seal/certs` | — | `[{serial, issuer_key, subject_key, token, expires_at_ms, created_at, revoked}]`. Quản trị viên hoặc quản trị viên cấp cao |
| `DELETE /api/seal/certs/{serial}` | — | `204`. Nhà phát hành hoặc quản trị viên cấp cao. Chỉ dừng phân phối |

```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}"
```

### Sao lưu khóa gốc của không gian làm việc (tùy chọn)

`PUT` / `GET` / `DELETE /api/seal/backup` lưu trữ một đốm màu `rsb1.` mờ đục cho mỗi tài khoản. `PUT` và `GET` chỉ dành cho quản trị viên cấp cao: blob là không gian làm việc **root** khóa riêng, được mã hóa trên máy khách bằng Argon2id (64 MiB, t=3, p=1) và ChaCha20-Poly1305. Cụm mật khẩu là tùy chọn và không có độ mạnh bắt buộc. **Với cụm mật khẩu trống hoặc yếu, bất kỳ ai nắm giữ blob (bao gồm Exchange) đều có thể giải mã khóa gốc và kiểm soát mọi thiết bị.** Tải lên là tùy chọn; khách hàng có thể xuất khóa gốc (`rsk1.`) và giữ nó ngoại tuyến. Exchange kiểm tra xem `blob` có bắt đầu bằng `rsb1.` và độ dài của nó không, đồng thời không bao giờ giải mã. `GET` được giới hạn ở 5 yêu cầu cho mỗi tài khoản mỗi giờ cho mỗi quy trình (HTTP 429). Việc đọc, ghi và xóa chỉ được kiểm tra bằng số byte. Phản hồi gửi `Cache-Control: no-store`.

| Phương pháp và đường dẫn | Thân hình | thành công |
| --- | --- | --- |
| `PUT /api/seal/backup` | `{"blob":"rsb1.…"}` | `{blob, updated_at}` |
| `GET /api/seal/backup` | — | `{blob, updated_at}` hoặc `404` khi không được lưu trữ. Tỷ lệ giới hạn |
| `DELETE /api/seal/backup` | — | `204`, kể cả khi không có hàng nào tồn tại |

```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}"
```

## Xác thực chuỗi gốc của phần mở rộng trình duyệt

Hoạt động của trình duyệt sử dụng các phiên WebSocket được niêm phong riêng, không phải HTTP một lần
thi hành. Tiện ích mở rộng tự tạo khóa toán tử Ed25519. Với một nhà điều hành
Mã thông báo mang nó nhận được `tenant_id` / `operator_id` từ `/api/auth/me`, sau đó gọi
`POST /api/seal/keys` với `{public_key, label, proof, issued_at_ms}`. Dấu hiệu PoP:

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

Khóa/chữ ký là base64 tiêu chuẩn (32/64 byte); thời gian phải nằm trong khoảng ±10
phút của đồng hồ máy chủ. Việc đăng ký chứng tỏ sự sở hữu chứ không phải sự ủy quyền.
Tiện ích mở rộng đồng bộ hóa `GET /api/seal/root`, `GET /api/seal/grants?mine=1` và
mã thông báo chứng chỉ được bao gồm trong các khoản trợ cấp hoặc được lấy từ
`GET /api/seal/certs/chain?subject_key=<url-encoded-key>`. Các khoản tài trợ phải được xác minh để
gốc không gian làm việc đáng tin cậy, trực tiếp hoặc thông qua chuỗi chứng chỉ hợp lệ, với
phạm vi, mức độ và hiệu lực của thiết bị chính xác. Đăng ký mã thông báo thiết bị vẫn còn
tách biệt khỏi luồng thư mục mã thông báo toán tử này.

Tiện ích mở rộng xác minh các địa chỉ gốc đến thông qua thư mục gốc của nó và bao gồm
sở hữu chữ ký xin chào/cấp/chuỗi trong `SealAccept.peer`. CLI/console xác minh bằng chứng đó
và yêu cầu `peer.operator_key == accept.device_key` trước khi chấp nhận chữ ký
phản hồi. Các tác nhân Rust sử dụng chính sách gốc thông thường của chúng khi tiện ích mở rộng hoạt động như
một nhà điều hành. Bằng chứng không hợp lệ/thiếu không đóng được; không có trình duyệt riêng
cửa hàng tin cậy/giao diện người dùng hoặc dự phòng kế thừa. TOFU gốc của thiết bị gốc và không gian làm việc vẫn còn;
tiện ích mở rộng lưu trữ thư mục gốc đầu tiên trên mỗi máy chủ/không gian làm việc và từ chối các thay đổi.

**Security:** xác minh root đầu tiên ngoài băng tần; PoP hoặc vai trò máy chủ không
phê duyệt gốc. Xem lại các khóa không xác định trước khi tự động phê duyệt máy khách gốc. Bảo vệ
hồ sơ/khóa điểm cuối và tập lệnh được lưu trữ. Xóa thư mục không thu hồi
thông tin xác thực đã ký được lưu trong bộ nhớ đệm. Exchange có thể xem siêu dữ liệu, giữ lại thông tin xác thực hoặc
từ chối dịch vụ; việc niêm phong không cô lập toán tử CDP đầy đủ thù địch.

## Chọn WebSocket hoặc HTTPS

| Cần | sử dụng |
| --- | --- |
| Lệnh, kiểm tra hoặc hủy bỏ công việc được giữ lại, cảm biến, thăm dò mạng, cập nhật, ubus, lệnh được ghim | Phiên WebSocket được niêm phong riêng |
| PTY tương tác, đầu ra phát trực tuyến, truyền tập tin, màn hình/cây/tấn Android | Phiên WebSocket được niêm phong riêng |
| Xác thực, kiểm kê thiết bị, thư mục khóa/cấp/chứng chỉ, sao lưu gốc | API HTTPS |
| Kiểm kê và hủy phiên định tuyến Exchange | Điểm cuối quản trị phiên HTTPS |
| Sự hiện diện của thiết bị trực tiếp và kết nối nhà điều hành lâu dài | WebSocket; hoạt động của thiết bị trên kết nối vẫn yêu cầu niêm phong |

Luồng vé trình duyệt (thông tin xác thực không bao giờ có trong URL WebSocket):

1. `POST /api/ws-ticket` với `Authorization: Bearer …`.
2. Kết nối với `GET /ws/browser` và gửi khung JSON đầu tiên `{"ticket":"…"}`.
3. Thiết lập bắt tay máy khách/thiết bị kín gốc trước khi gửi bất kỳ thao tác thiết bị nào. Chuỗi cấp và chứng chỉ của nhà điều hành phải xác thực dựa trên thư mục gốc được thiết bị tin cậy.

Máy khách gốc sử dụng `GET /ws/operator` với xác thực của nhà điều hành và yêu cầu phiên kín tương tự. Chỉ riêng kết nối WebSocket hoặc đăng nhập tài khoản thành công không cho phép thiết bị hoạt động. Xem [Bảng điều khiển web và Web Push](/docs/vi/web-console-and-notifications.md), [protocol](/docs/vi/websocket-protocol.md) và [phiên kín](/docs/vi/sealed-sessions.md).

## Bảng ghi trạng thái lỗi

| Trạng thái | Nguyên nhân điển hình |
| --- | --- |
| `400` | Đầu vào hành động không hợp lệ, chẳng hạn như lệnh trống hoặc URL nguồn cập nhật không hợp lệ; xác nhận xảy ra trước khi từ chối phiên kín |
| `401` | Mã thông báo Bearer bị thiếu hoặc không hợp lệ |
| `403` | Quyền tài khoản hoặc thiết bị không cho phép yêu cầu |
| `409` | Hành động của thiết bị HTTP hợp lệ, được ủy quyền yêu cầu phiên WebSocket được niêm phong; không có sự gửi thiết bị nào xảy ra |
| `404` | Thiếu mục nhập thư mục hoặc gốc không gian làm việc chưa được xuất bản, theo hợp đồng điểm cuối |
| `429` | Giới hạn tốc độ dành riêng cho điểm cuối, bao gồm cả các lần đọc bản sao lưu gốc của không gian làm việc |
| `503` | Kiểm kê phiên không thể tìm thấy thiết bị ở trạng thái trung tâm Exchange cục bộ |

Các tuyến HTTP hành động của thiết bị không chờ đầu ra agent, vì vậy, kết quả được ủy quyền thông thường của chúng là `409`, không phải là thực thi thành công, lỗi gửi ngoại tuyến hoặc hết thời gian thực thi HTTP. Các lỗi từ siêu dữ liệu, xác thực và API thư mục vẫn giữ nguyên ý nghĩa dành riêng cho điểm cuối của chúng.

## Kế hoạch xác nhận thủ công

Sử dụng tài khoản thử nghiệm và thiết bị có quyền đã biết. Kiểm tra từ chối HTTP không yêu cầu thực thi công việc trên thiết bị:

1. Xác thực và liệt kê các thiết bị qua HTTPS; xác minh quyền của tài khoản và thiết bị.
2. Gửi yêu cầu hợp lệ tới từng lộ trình hành động của thiết bị trong bảng từ chối. Yêu cầu HTTP `409` và xác nhận rằng không có hành động nào đạt tới agent, bao gồm cả các lần đọc lệnh được giữ lại và chạy lệnh được ghim.
3. Kiểm tra riêng thông tin đăng nhập không hợp lệ, tài khoản bị cấm và yêu cầu nhập không hợp lệ. Mong đợi các lỗi xác thực, quyền và xác thực thay vì giả sử mọi yêu cầu đều trả về `409`.
4. Đọc kho lưu trữ phiên Exchange. Đối với phiên dùng một lần được chọn rõ ràng, hãy hủy kiểm tra và xác minh `released`; một phiên bị thiếu sẽ trả về `released:false`. Danh sách phiên trên thiết bị ngoại tuyến cục bộ trả về `503`.
5. Hoàn thành phê duyệt và cấp quyền gốc, sau đó chạy thao tác CLI gốc vô hại như `remote-cli exec "$DEVICE_ID" -- uname -a`. Xác minh nó thành công thông qua cái bắt tay kín. Cấp thiết bị bị thiếu hoặc không hợp lệ phải đóng không thành công.
6. Xác thực đăng ký khóa, cấp/truy xuất chứng chỉ, sao lưu gốc tùy chọn và xác thực chuỗi gốc của trình duyệt một cách độc lập; các API HTTPS đó vẫn có sẵn theo các hợp đồng ở trên.

## Đích xuất nhật ký kiểm toán

Chỉ hỗ trợ PostgreSQL Exchange, trong phạm vi không gian làm việc đã xác thực. Chỉ quản trị viên và superadmin được sử dụng; tài khoản khác nhận 403. Không hỗ trợ exchange-worker; chưa có giao diện cấu hình trên 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
```

Chế độ throttled mặc định xuất các bản ghi kiểm toán đã lưu: văn bản vượt 256 KiB giữ 250 KiB đầu và 6 KiB cuối, tệp được biểu diễn bằng hàm băm, luồng được xuất sau khi phiên kết thúc. Chế độ full thu thập trước khi cắt ngắn, chia JSON gốc và nội dung nhị phân thành các khối (mặc định 1 MiB), kèm ID thao tác/tài sản, số thứ tự, độ lệch, dấu kết thúc, SHA-256 và tổng kích thước. Với phiên kín, cả hai chế độ chỉ xuất siêu dữ liệu và tham chiếu/hàm băm của bản mã; không chế độ nào khôi phục văn bản rõ tại thiết bị đầu cuối.

Bí mật và tiêu đề được mã hóa và hiển thị lại dưới dạng ***. PATCH giữ nguyên giá trị ***; tiêu đề có giá trị null sẽ bị xóa. Việc gửi dùng hàng đợi FIFO bền vững bất đồng bộ và thử lại với thời gian chờ tăng theo cấp số nhân. Khi đầy, hàng đợi throttled loại bỏ và đếm bản ghi cũ nhất; hàng đợi full trên đĩa áp dụng cơ chế làm chậm bên gửi. Trạng thái bao gồm thành công/thất bại, tồn đọng, số bản ghi bị loại bỏ và khoảng trống. Xóa đích sẽ loại bỏ dữ liệu đang chờ và ghi lại số lượng. Thay đổi cấu hình được kiểm toán trong cùng giao dịch.

Webhook gửi mảng events trong JSON với dữ liệu byte dạng base64. Xác minh X-Remote-Timestamp và X-Remote-Signature: sha256=<hex> bằng HMAC-SHA256(timestamp + "." + raw body). OTel dùng OTLP/HTTP JSON /v1/logs, body.bytesValue cho các khối, thuộc tính remote.device.id / remote.operator / remote.action và service.name có thể cấu hình. gRPC cần Collector làm cầu nối; không có bộ xuất gRPC trực tiếp.

Bên dưới là ví dụ cấu hình mặc định, payload webhook, xác minh chữ ký và Collector. Xác minh phần thân HTTP thô; lưu bền vững và loại trùng theo delivery_id trước khi trả về 2xx. Bật allow_structured_metadata trong Loki 3.x, thêm nguồn dữ liệu Loki trong Grafana và truy vấn {service_name="remote-exchange"}. Để dùng gRPC, đổi exporters của pipeline thành [otlp/grpc]. Dùng TLS và xác thực trong môi trường sản xuất.

```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]
```

| Trường kiểm toán | Trường 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.* |
| Sự kiện đã lưu | body.stringValue |
