Skip to content

Commit 76cff0e

Browse files
committed
refactor: 引入上下文结构体,迁移 relay 部署文档,更新 README
- host/join 引入 HostContext/JoinContext 消除 too_many_arguments - iroh-relay.service 迁移至 docs/deploy/service.md 并补充预编译下载 - relay 编译脚本拆分为 scripts/deploy/build.sh 和 build.ps1 - Justfile relay 配方从 4 条简化为 1 条跨平台调用 - README 同步 profile.toml 持久化变更与自建 Relay 链接 - 修复 clippy derivable_impls/unnecessary_lazy_evaluations/needless_lifetimes
1 parent 3ad9539 commit 76cff0e

12 files changed

Lines changed: 233 additions & 259 deletions

File tree

Justfile

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
44
default:
55
@just --list
66

7-
# 安装到 ~/.cargo/bin
7+
# 安装到 sculk 到 ~/.cargo/bin
88
install:
99
cargo install --path cli
1010

@@ -15,7 +15,7 @@ install-tui:
1515
# 安装全部客户端
1616
install-all: install install-tui
1717

18-
# 卸载
18+
# 卸载 sculk
1919
uninstall:
2020
cargo uninstall sculk-cli
2121

@@ -32,15 +32,15 @@ check:
3232
cargo check --workspace
3333
cargo clippy --workspace -- -D warnings
3434

35-
# 测试(离线优先,和 CI 口径一致)
35+
# 离线测试
3636
test:
3737
cargo nextest run --workspace --features sculk-core/ci --no-tests=pass
3838

39-
# 网络集成测试(需要可用网络环境)
39+
# 网络集成测试
4040
test-e2e:
4141
cargo nextest run -p sculk-core --test p2p_test --no-tests=pass
4242

43-
# 全量测试(稳定测试 + 网络集成测试)
43+
# 全量测试
4444
test-all: test test-e2e
4545

4646
# 格式化
@@ -51,29 +51,13 @@ fmt:
5151
doc:
5252
cargo doc --workspace --no-deps --open
5353

54-
# 内部: 交叉编译 iroh-relay
55-
[unix]
56-
_relay-build target linker env_var artifact:
57-
#!/usr/bin/env bash
58-
set -euo pipefail
59-
which "{{ linker }}" > /dev/null 2>&1 || { echo "缺少 musl-cross 工具链,请先安装: brew install filosottile/musl-cross/musl-cross"; exit 1; }
60-
rustup target list --installed | grep -q "{{ target }}" || rustup target add "{{ target }}"
61-
export {{ env_var }}="{{ linker }}"
62-
cargo install iroh-relay --features server --target "{{ target }}" --root target/relay
63-
mv target/relay/bin/iroh-relay "target/relay/bin/{{ artifact }}"
64-
echo "产物: target/relay/bin/{{ artifact }}"
65-
66-
# 编译 iroh-relay linux-amd64
67-
[unix]
68-
[group('relay')]
69-
relay-build-x86_64: (_relay-build "x86_64-unknown-linux-musl" "x86_64-linux-musl-gcc" "CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER" "iroh-relay-linux-amd64")
70-
71-
# 编译 iroh-relay linux-arm64
54+
# 编译 iroh-relay relay 服务端
7255
[unix]
7356
[group('relay')]
74-
relay-build-aarch64: (_relay-build "aarch64-unknown-linux-musl" "aarch64-linux-musl-gcc" "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER" "iroh-relay-linux-arm64")
57+
relay-build target='all':
58+
bash scripts/deploy/build.sh {{ target }}
7559

76-
# 编译全部架构的 iroh-relay
77-
[unix]
60+
[windows]
7861
[group('relay')]
79-
relay-build-all: relay-build-x86_64 relay-build-aarch64
62+
relay-build target='all':
63+
pwsh scripts/deploy/build.ps1 -Target {{ target }}

README.md

Lines changed: 35 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
- `sculk-tui`:终端图形客户端(TUI)
66
- `sculk-core`:可复用隧道核心库
77

8-
> 项目名来自 Minecraft 的幽匿(Sculk)
8+
> Sculk(幽匿)是 Minecraft 深暗之域中悄然蔓延的脉络,无声地在节点间传递信号。sculk 做的事类似——在玩家之间建立隐匿的隧道,让连接自然发生
99
1010
## 项目结构
1111

@@ -26,116 +26,12 @@
2626
`join` 端把远端隧道映射到本地端口(默认 `30000`),MC 客户端连本地即可。
2727
链路优先直连(NAT 打洞),失败回退 relay。
2828

29-
### 系统架构全景
30-
31-
```mermaid
32-
flowchart LR
33-
subgraph U["用户入口"]
34-
CLI["sculk CLI\nhost | join | relay"]
35-
TUI["sculk-tui\n建房 | 加入 | 中继 | 日志"]
36-
end
37-
38-
subgraph C["sculk-core"]
39-
API["IrohTunnel::host / join"]
40-
TICKET["Ticket\nsculk://<EndpointId>?relay=<url>"]
41-
CFG["TunnelConfig\npassword / max_players / max_retries / event_delay"]
42-
EVT["TunnelEvent\nConnected / Disconnected / PathChanged / Reconnecting / Error"]
43-
SNAP["ConnectionSnapshot\nis_relay / rtt / tx / rx"]
44-
end
45-
46-
subgraph P["本地持久化 data_dir()/sculk"]
47-
KEY["secret.key\n32-byte SecretKey"]
48-
RELAY["relay.conf\n自定义 Relay URL"]
49-
end
50-
51-
subgraph N["网络层 (iroh + QUIC)"]
52-
EPH["Host Endpoint"]
53-
EPJ["Join Endpoint"]
54-
RELAYNET["Relay\nn0 默认或自建"]
55-
end
56-
57-
subgraph M["Minecraft TCP 转发"]
58-
HOSTMC["房主服务端\n127.0.0.1:25565"]
59-
JOININ["玩家入口\n127.0.0.1:30000"]
60-
end
61-
62-
CLI --> API
63-
TUI --> API
64-
CLI --> CFG
65-
TUI --> CFG
66-
API --> TICKET
67-
API --> EVT
68-
API --> SNAP
69-
70-
CLI --> KEY
71-
TUI --> KEY
72-
CLI --> RELAY
73-
TUI --> RELAY
74-
KEY --> API
75-
RELAY --> API
76-
77-
API --> EPH
78-
API --> EPJ
79-
EPH -->|直连 优先| EPJ
80-
EPJ -->|直连 回程| EPH
81-
EPH -->|中继 回退| RELAYNET
82-
RELAYNET -->|中继 转发| EPH
83-
EPJ -->|中继 回退| RELAYNET
84-
RELAYNET -->|中继 转发| EPJ
85-
86-
HOSTMC -->|TCP| EPH
87-
EPH -->|TCP| HOSTMC
88-
JOININ -->|TCP| EPJ
89-
EPJ -->|TCP| JOININ
90-
```
91-
92-
### 建房/加入时序
93-
94-
```mermaid
95-
sequenceDiagram
96-
participant H as 房主 (sculk host / TUI 建房)
97-
participant C as sculk-core
98-
participant R as Relay (n0/自建)
99-
participant J as 玩家 (sculk join / TUI 加入)
100-
participant MCJ as 玩家 MC 客户端
101-
participant MCH as 房主 MC 服务端
102-
103-
H->>C: 读取/生成 secret.key,解析 relay.conf
104-
C-->>H: 生成 Ticket(sculk://...)
105-
H->>J: 分享 Ticket
106-
J->>C: join(ticket, local_port, config)
107-
C->>C: 密码校验 + max_players 校验
108-
109-
alt NAT 打洞成功
110-
C-->>J: 建立 QUIC 直连
111-
else NAT 打洞失败
112-
C->>R: 回退 relay
113-
R-->>J: 建立 relay 路径
114-
end
115-
116-
MCJ->>J: 连接 127.0.0.1:30000
117-
J->>C: 转发为 QUIC stream
118-
C->>MCH: 转发到 127.0.0.1:25565
119-
120-
C-->>H: TunnelEvent(PlayerJoined / PathChanged / Error ...)
121-
C-->>J: TunnelEvent(Connected / Reconnecting / Disconnected ...)
122-
```
29+
连接流程:
12330

124-
### TUI 状态机
125-
126-
```mermaid
127-
stateDiagram-v2
128-
[*] --> Idle
129-
Idle --> Starting: Enter(建房/加入)
130-
Starting --> Active: HostStarted / JoinConnected
131-
Starting --> Idle: StartFailed
132-
Active --> Stopping: Enter(停止)
133-
Stopping --> Idle: Closed
134-
Idle --> Idle: 中继模式 Enter 应用 relay.conf
135-
Active --> Active: TunnelEvent 更新日志与连接快照
136-
Idle --> [*]: Esc 双击(3秒内)
137-
Active --> [*]: Esc 双击(3秒内)
138-
```
31+
1. 房主启动 `sculk host`,读取/生成密钥,生成 `sculk://...` 票据并分享
32+
2. 玩家通过 `sculk join "sculk://..."` 连接,经密码校验和人数校验后建立 QUIC 隧道
33+
3. 隧道在两端之间双向转发 TCP 流量:玩家 MC 客户端 → 本地端口 → QUIC → 房主 MC 服务端
34+
4. 运行时通过 `TunnelEvent` 推送状态变化(玩家加入/离开、路径切换、重连等)
13935

14036
## 安装
14137

@@ -251,12 +147,32 @@ sculk-tui
251147
## 配置与数据目录
252148

253149
默认位于系统 `data_dir()/sculk`
254-
- `secret.key`:私钥文件
255-
- `relay.conf`:自定义中继地址
150+
- macOS:`~/Library/Application Support/sculk/`
151+
- Linux:`~/.local/share/sculk/`
152+
- Windows:`%APPDATA%\sculk\`
153+
154+
文件列表:
155+
- `secret.key`:32 字节 iroh 私钥,持久化后 ticket 可跨重启保持稳定
156+
- `profile.toml`:用户偏好配置(端口、中继、上次票据等)
157+
158+
`profile.toml` 结构示例:
159+
160+
```toml
161+
[host]
162+
port = 25565
163+
164+
[join]
165+
port = 30000
166+
last_ticket = "sculk://..."
167+
168+
[relay]
169+
custom = false
170+
# url = "https://your-relay.example.com"
171+
```
256172

257173
说明:
258-
- 私钥持久化后,ticket 可跨重启保持稳定
259174
- `--new-key` 会重置身份并改变 ticket
175+
- 未出现的字段自动取默认值,增删字段不会导致旧配置解析失败
260176

261177
## 开发
262178

@@ -275,11 +191,12 @@ cargo install cargo-nextest --locked
275191

276192
```sh
277193
just check # fmt + check + clippy
278-
just test # 与 CI 对齐(离线优先)
194+
just test # 离线测试
279195
just test-e2e # 网络集成测试
280196
just test-all # 全量测试
281197
just fmt # 格式化
282198
just doc # 生成文档
199+
just relay-build # 交叉编译 iroh-relay
283200

284201
just install # 安装 sculk
285202
just install-tui # 安装 sculk-tui
@@ -298,6 +215,10 @@ Release 会同时构建两个客户端(`sculk` + `sculk-tui`):
298215
- macOS arm64:`sculk-darwin-arm64` / `sculk-tui-darwin-arm64`
299216
- Windows amd64:`sculk-windows-amd64.exe` / `sculk-tui-windows-amd64.exe`
300217

218+
## 自建 Relay
219+
220+
默认使用 n0 公共 relay,如需自建请参考 [部署文档](docs/deploy/service.md)
221+
301222
## 网络与 NAT 说明
302223

303224
- 理想路径:直连(延迟更低)

core/src/persist/profile.rs

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const PROFILE_FILE: &str = "profile.toml";
1313
///
1414
/// 各字段均实现 [`Default`],未出现在文件中的键自动取默认值,
1515
/// 因此增删字段不会导致旧版配置文件解析失败。
16-
#[derive(Debug, Clone, Serialize, Deserialize)]
16+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1717
pub struct Profile {
1818
#[serde(default)]
1919
pub host: HostProfile,
@@ -43,7 +43,7 @@ pub struct JoinProfile {
4343
}
4444

4545
/// relay 偏好配置,对应 `[relay]` TOML 节。
46-
#[derive(Debug, Clone, Serialize, Deserialize)]
46+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
4747
pub struct RelayProfile {
4848
/// `true` 启用自建中继,`false` 使用 iroh 内置 n0 中继服务器组。
4949
#[serde(default)]
@@ -53,16 +53,6 @@ pub struct RelayProfile {
5353
pub url: Option<String>,
5454
}
5555

56-
impl Default for Profile {
57-
fn default() -> Self {
58-
Self {
59-
host: HostProfile::default(),
60-
join: JoinProfile::default(),
61-
relay: RelayProfile::default(),
62-
}
63-
}
64-
}
65-
6656
impl Default for HostProfile {
6757
fn default() -> Self {
6858
Self {
@@ -80,15 +70,6 @@ impl Default for JoinProfile {
8070
}
8171
}
8272

83-
impl Default for RelayProfile {
84-
fn default() -> Self {
85-
Self {
86-
custom: false,
87-
url: None,
88-
}
89-
}
90-
}
91-
9273
fn default_mc_port() -> u16 {
9374
crate::DEFAULT_MC_PORT
9475
}
@@ -149,12 +130,10 @@ impl Profile {
149130
&self,
150131
custom: Option<&str>,
151132
) -> anyhow::Result<Option<crate::tunnel::RelayUrl>> {
152-
let url_str = custom.or_else(|| {
153-
if self.relay.custom {
154-
self.relay.url.as_deref()
155-
} else {
156-
None
157-
}
133+
let url_str = custom.or(if self.relay.custom {
134+
self.relay.url.as_deref()
135+
} else {
136+
None
158137
});
159138
match url_str {
160139
Some(s) => {
@@ -196,10 +175,7 @@ mod tests {
196175
assert_eq!(p2.host.port, 12345);
197176
assert_eq!(p2.join.last_ticket.as_deref(), Some("sculk://test"));
198177
assert!(p2.relay.custom);
199-
assert_eq!(
200-
p2.relay.url.as_deref(),
201-
Some("https://relay.example.com")
202-
);
178+
assert_eq!(p2.relay.url.as_deref(), Some("https://relay.example.com"));
203179
}
204180

205181
#[test]

core/src/tunnel/event.rs

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -47,45 +47,29 @@ impl Default for TunnelConfig {
4747
#[derive(Debug, Clone)]
4848
pub enum TunnelEvent {
4949
/// host 侧:新玩家建立连接。
50-
PlayerJoined {
51-
id: String,
52-
},
50+
PlayerJoined { id: String },
5351
/// host 侧:玩家断开连接,`reason` 为 QUIC 层关闭原因。
54-
PlayerLeft {
55-
id: String,
56-
reason: String,
57-
},
52+
PlayerLeft { id: String, reason: String },
5853
/// join 侧:与 host 的 QUIC 连接已建立。
5954
Connected,
6055
/// join 侧:与 host 的连接断开,`reason` 为关闭原因。若将重连,随后会发送 [`Self::Reconnecting`]。
61-
Disconnected {
62-
reason: String,
63-
},
56+
Disconnected { reason: String },
6457
/// 选中路径切换或 RTT 变化时触发,`event_delay` 控制发送节流。
6558
PathChanged {
6659
remote_id: String,
6760
is_relay: bool,
6861
rtt_ms: u64,
6962
},
7063
/// join 侧:即将发起第 `attempt` 次重连。
71-
Reconnecting {
72-
attempt: u32,
73-
},
64+
Reconnecting { attempt: u32 },
7465
/// join 侧:重连成功。
7566
Reconnected,
7667
/// host 侧:密码验证失败,连接已被关闭。
77-
AuthFailed {
78-
id: String,
79-
},
68+
AuthFailed { id: String },
8069
/// host 侧:连接被主动拒绝,如服务器满员时 `reason` 为 `"server full"`。
81-
PlayerRejected {
82-
id: String,
83-
reason: String,
84-
},
70+
PlayerRejected { id: String, reason: String },
8571
/// 非致命的内部或 I/O 错误,隧道仍在运行。
86-
Error {
87-
message: String,
88-
},
72+
Error { message: String },
8973
}
9074

9175
/// 单条连接的瞬时状态快照,由 [`IrohTunnel::connections`](crate::tunnel::IrohTunnel::connections) 返回。

0 commit comments

Comments
 (0)