# 開發說明

> HTML: https://remote.ivjn.us/docs/zh-TW/development/

這份文件說明如何在本倉庫裡建置、測試並在本機把 exchange 跑起來。部署步驟、協議欄位表和各平臺安裝細節仍以專題文件為準，文末有連結。

## 工具鏈

工作區使用 Rust 2021，要求 **Rust 1.88 或更新版本**。版本寫在倉庫根 `Cargo.toml` 的 `rust-version` 欄位裡。

## 工作區與二進位

Rust 工作區包含九個成員：八個位於 `crates/`，擴充 SDK 位於 `sdk/extend/rust`。協議型別只定義在 `crates/proto`，`exchange`、`agent`、`cli` 都依賴這一個 crate，不要在某一端再抄一套訊息結構。

| crate | 路徑 | 產物 |
|---|---|---|
| `proto` | `crates/proto` | 庫。控制訊息、二進位幀和共享資料結構只在這裡 |
| `exchange` | `crates/exchange` | 二進位 `exchange`（包名與二進位名相同） |
| `agent` | `crates/agent` | 二進位 `remote-agent` |
| `cli` | `crates/cli` | 二進位 `remote-cli` |
| `updater` | `crates/updater` | 庫，供 `remote-agent` 與 `remote-cli` 做自更新 |
| `telemetry` | `crates/telemetry` | 庫，三端共用的異常與紀錄上報 |
| `tls-client` | `crates/tls-client` | 函式庫，共用的 rustls 用戶端 TLS 策略與可選代理連線 |
| `worker-proto` | `crates/worker-proto` | 函式庫，Workers Exchange 的協定驗證與憑證雜湊 |
| `remote-extend-sdk` | `sdk/extend/rust` | 函式庫，擴充 provider 的框架與契約 |

`exchange` 的原始檔入口是 `crates/exchange/src/main.rs`，沒有單獨的 `[[bin]]` 表，所以 `cargo build -p exchange` 得到的就是 `exchange`。`agent` 與 `cli` 在各自的 `Cargo.toml` 裡把二進位名寫成 `remote-agent` 和 `remote-cli`。

連線表按職責拆開：

- `crates/exchange/src/hub.rs` 管線上裝置、重連頂替和 epoch
- `crates/exchange/src/hub/session_table.rs` 管運維側編號與裝置側編號的翻譯
- `crates/exchange/src/hub/outbound.rs` 管單條連線的有界傳送佇列

## 建置

在倉庫根目錄執行。

釋出檔（`opt-level = 2`、thin LTO，保留 unwind）會編出伺服器端、命令列和 agent：

```bash
cargo build --release
```

二進位在：

- `target/release/exchange`
- `target/release/remote-cli`
- `target/release/remote-agent`

`release` 保留 unwind 是因為 exchange 是長駐多連線程序。單個連線任務 panic 時，unwind 讓 tokio 能停掉這一條任務，而不是把同程序裡其它裝置的會話一起帶走。

裝置端還要一個體積優先的 profile（`opt-level = "z"`、LTO、`panic = "abort"`、`strip`、`codegen-units = 1`）：

```bash
cargo build -p agent --no-default-features --profile release-small
```

產物是 `target/release-small/remote-agent`。agent 崩潰後由 procd、systemd 或計劃任務拉起，所以這裡可以用 `panic = "abort"` 換體積。

資料庫不參與編譯。`crates/exchange/src/db.rs` 使用 sqlx 的執行時查詢，不用 `query!` 巨集，因此沒有資料庫也能 `cargo build`。

## 測試

```bash
cargo test --workspace
```

Windows 上不會執行依賴 Unix `/bin/sh`（或 OpenWrt 上的 `/bin/ash`）和 PTY 的測試。`crates/agent/src/pty.rs` 的測試模組由 `#[cfg(all(test, unix))]` 包住，非 Unix 平臺不編譯這部分。PTY 實現會依次嘗試 `/bin/ash` 和 `/bin/sh`。`exec.rs` 裡的程序組與 Unix 訊號路徑同樣只在 Unix 編譯；Windows 上的命令測試走 PowerShell，不呼叫 `/bin/sh`。改了 `exec.rs` 或 `pty.rs` 的 Unix 路徑時，需要在 Linux 上再跑一遍 `cargo test --workspace`，只看 Windows 的結果不夠。

單 crate 可以縮小範圍，例如 `cargo test -p proto` 或 `cargo test -p exchange -- hub::`。

## 本機啟動 exchange

先準備 PostgreSQL，再把連線串放進 `DATABASE_URL`。這個變數對應 `exchange` 的 `--database-url`（見 `crates/exchange/src/config.rs`）。程序啟動時 `db::connect` 會執行編譯期嵌入的遷移：

```rust
sqlx::migrate!("../../migrations")
```

遷移檔案在倉庫根的 `migrations/`，啟動時自動跑完，不需要 sqlx-cli。

審計操作還需要審計加密金鑰，以下範例會產生本機測試金鑰。請妥善保存，之後啟動時重用原金鑰；不要每次重新產生，否則既有審計詳細資料將無法解密。

```bash
export DATABASE_URL='postgres://luci:luci@127.0.0.1:5432/luci'
export AUDIT_ENCRYPTION_KEY="$(python -c 'import base64, secrets; print(base64.b64encode(secrets.token_bytes(32)).decode())')"
cargo run -p exchange -- --listen 127.0.0.1:8080
```

PowerShell 下先設定環境變數再啟動：

```powershell
$env:DATABASE_URL = "postgres://luci:luci@127.0.0.1:5432/luci"
$env:AUDIT_ENCRYPTION_KEY = python -c "import base64, secrets; print(base64.b64encode(secrets.token_bytes(32)).decode())"
cargo run -p exchange -- --listen 127.0.0.1:8080
```

已經編過釋出二進位時，把上面的 `cargo run -p exchange --` 換成 `./target/release/exchange` 即可。監聽地址的預設值是 `0.0.0.0:8080`，本機除錯建議顯式綁到 `127.0.0.1`。

庫裡還沒有任何啟用中的運維帳號時，啟動過程會自舉一個管理員，並把令牌打在這一次的啟動紀錄裡。明文只出現這一次。完整的部署、反向代理和帳號說明在倉庫的 `docs/deployment.md`。該頁包含伺服器地址，不在本站公開。

## 提交前檢查

在儲存庫根目錄執行以下檢查。修改 Rust 時先用單一 crate 測試縮短回饋時間，再完成工作區檢查；格式檢查發現與本次修改無關的舊問題時，只格式化改動檔案，避免順帶重排其他程式碼。Android、瀏覽器擴充功能與行動控制端仍需各自的建置與測試，Rust 測試不能取代。

```sh
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
```

## 維護文件

行為變更時先核對實作，再更新 `docs/` 中的儲存庫指南。網站主題同時維護 `website/docs/<topic>.md` 參考來源與 `website/docs/content/<locale>/<topic>.md` 九種語言內文；命令、欄位、程式碼範例與標題層級必須一致。頁面與搜尋索引由腳本產生，不能只手改 HTML。以下命令從儲存庫根目錄執行，需要 Python 3.11+、markdown-it-py 與 Node.js：

```sh
python -m pip install markdown-it-py
python scripts/build-docs.py
python scripts/build-docs.py --check
python scripts/test-docs.py
python scripts/test-docs-markdown.py
node scripts/test-docs-locale.js
python scripts/test-build-docs.py
python scripts/test-site-seo.py
```

產生檢查也會逐位元組比較所有產生檔案，缺少或過期時失敗，且不寫入檔案；CI 在重新產生前執行此檢查。

檢查會驗證翻譯完整性、程式碼範例、標題結構、頁面連結、錨點與語言路由。產生的頁面須與來源一起提交。建置文件僅更新本機檔案；網站部署與下載包發布是獨立操作。

## 相關文件

- [技術說明](/docs/zh-TW/architecture.md) —— 外連、會話翻譯、epoch、有界佇列和令牌儲存
- [使用說明](/docs/zh-TW/getting-started.md) —— 登入、新增裝置、啟動 agent、執行命令和開啟 shell
- 部署指南在倉庫 `docs/deployment.md`（含伺服器地址，不在本站公開）
- [線路協議](/docs/zh-TW/websocket-protocol.md)
- [多平臺裝置部署](/docs/zh-TW/platform-installation.md)
