Skip to content

Commit a2ceac7

Browse files
committed
release: v0.1.10
1 parent e00f205 commit a2ceac7

10 files changed

Lines changed: 226 additions & 240 deletions

File tree

CHANGELOG.en.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ All notable changes to `ii` are documented here. The default Chinese version is
66

77
Nothing yet.
88

9+
## 0.1.10 - 2026-07-17
10+
11+
### Changed
12+
13+
- Added `ii relay --tls <domain> --cert <path> --key <path>` for HTTPS relays using operator-provided PEM certificate and key files.
14+
- TLS mode no longer exposes a public HTTP relay; certificate files and the domain are owned by the operator.
15+
- Removed ACME certificate issuance, certificate renewal, and QUIC address discovery while retaining the HTTP-only default relay.
16+
- Made `ii doctor` check `3340/tcp` by default.
17+
18+
### Documentation
19+
20+
- Updated manual-certificate HTTPS and relay port guidance.
21+
922
## 0.1.9 - 2026-07-17
1023

1124
### Added

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@
66

77
暂无。
88

9+
## 0.1.10 - 2026-07-17
10+
11+
### 变更
12+
13+
- `ii relay` 新增 `--tls <domain> --cert <path> --key <path>`,使用运维方提供的 PEM 证书和私钥启动 HTTPS relay。
14+
- TLS 模式不再开放公网 HTTP relay;证书和域名必须由运维方负责。
15+
- 移除 ACME 自动证书、自动续期和 QUIC 地址发现,默认 relay 保持纯 HTTP。
16+
- `ii doctor` 默认检查 `3340/tcp`
17+
18+
### 文档
19+
20+
- 更新 HTTPS 手工证书和 relay 端口说明。
21+
922
## 0.1.9 - 2026-07-17
1023

1124
### 新增

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ii"
3-
version = "0.1.9"
3+
version = "0.1.10"
44
edition = "2024"
55
rust-version = "1.91"
66
license = "MIT"

README.en.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,13 @@ Clients use it with:
197197
ii send .\video.mp4 --relay http://SERVER_PUBLIC_IP:3340
198198
```
199199

200-
Configure HTTPS, a domain, ACME certificates, or QUIC address discovery only when needed by editing `relay.toml` and starting with `--config`. See [ii.md](ii.md) for the full configuration and Nginx reverse-proxy setup. Plain HTTP is not suitable for a long-lived public deployment.
200+
For HTTPS and a domain, explicitly provide an existing certificate and private key:
201+
202+
```powershell
203+
ii relay --tls relay.example.com -H 8443 --cert D:\certs\fullchain.pem --key D:\certs\privkey.pem
204+
```
205+
206+
Clients use `https://relay.example.com:8443`. `ii` does not issue or renew certificates; the operator owns those files. See [ii.md](ii.md) for the full configuration. Plain HTTP is not suitable for a long-lived public deployment.
201207

202208
## Full Manual
203209

@@ -209,7 +215,7 @@ Release changes are documented in [CHANGELOG.en.md](CHANGELOG.en.md). The defaul
209215

210216
## Version
211217

212-
The current version is managed by Git tags. This repository currently uses `v0.1.9`.
218+
The current version is managed by Git tags. This repository currently uses `v0.1.10`.
213219

214220
## License
215221

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,13 @@ ii relay
208208
ii send .\video.mp4 --relay http://服务器公网IP:3340
209209
```
210210

211-
需要 HTTPS、域名、ACME 证书或 QUIC 地址发现时,再编辑 `relay.toml` 并通过 `--config` 启动。完整配置和 Nginx 反向代理方式见 [ii.md](ii.md)。纯 HTTP 不适合不受信任的公网长期部署。
211+
需要 HTTPS 和域名时,显式提供已有证书和私钥:
212+
213+
```powershell
214+
ii relay --tls relay.example.com -H 8443 --cert D:\certs\fullchain.pem --key D:\certs\privkey.pem
215+
```
216+
217+
客户端使用 `https://relay.example.com:8443``ii` 不申请、不续期证书;证书文件由运维方负责。完整配置见 [ii.md](ii.md)。纯 HTTP 不适合不受信任的公网长期部署。
212218

213219
## 详细手册
214220

@@ -220,7 +226,7 @@ ii send .\video.mp4 --relay http://服务器公网IP:3340
220226

221227
## 版本
222228

223-
当前版本由 Git tag 管理。仓库内已使用 `v0.1.9`
229+
当前版本由 Git tag 管理。仓库内已使用 `v0.1.10`
224230

225231
## 许可证
226232

ii.md

Lines changed: 25 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
```text
1212
ii send [<path>] [--name <name>] [-t] [-c] [-o <path>] [--s3] [--webdav] [--profile <name>] [-d] [-p] [--local] [--relay <url>] [--no-relay]
1313
ii recv <ticket> [-o <dir>] [--stdout] [--overwrite] [--resume] [--local] [--trace]
14-
ii relay [--config <path>] [--http <port>] [--https <port>] [--quic <port>] [--metrics <port>]
14+
ii relay [--config <path>] [--port <port>] [--metrics <port>] [--tls <domain> --cert <path> --key <path>]
1515
ii doctor
1616
ii version
1717
```
@@ -262,7 +262,7 @@ iroh-relay
262262
ii relay
263263
ii relay --config .\relay.toml
264264
ii relay -H 8080
265-
ii relay --config .\relay.toml -S 8443 -Q 7843
265+
ii relay --tls relay.example.com -H 8443 --cert .\fullchain.pem --key .\privkey.pem
266266
```
267267

268268
### 默认启动
@@ -286,7 +286,7 @@ ii send .\video.mp4 --relay http://203.0.113.10:3340
286286

287287
这是默认路径。公网 HTTP 没有 TLS 保护,只适合临时、受信任的网络;要长期公开提供 relay,再配置 HTTPS。
288288

289-
旧版自动生成的空 LetsEncrypt 模板会被识别并忽略,`ii relay` 会回退到 HTTP,不再因缺域名启动失败。
289+
旧版自动生成的不完整 TLS 模板会被识别并忽略,`ii relay` 会回退到 HTTP,不再因缺域名启动失败。
290290

291291
### 配置文件路径
292292

@@ -314,79 +314,54 @@ ii send .\video.mp4 --relay http://203.0.113.10:8080
314314

315315
端口参数:
316316

317-
- `--http <port>` / `-H <port>`:HTTP 端口,对应 `http_bind_addr`
318-
- `--https <port>` / `-S <port>`:HTTPS 端口,必须已有 `[tls]` 配置
319-
- `--quic <port>` / `-Q <port>`:QUIC 地址发现端口,必须已有 `[tls]` 配置
317+
- 默认或未传 `--tls` 时,`--port <port>` / `-H <port>` 是 HTTP relay 端口,对应 `http_bind_addr`
318+
- 传了 `--tls` 时,`-H <port>` 改为 HTTPS relay 端口,默认 `443`
320319
- `--metrics <port>` / `-M <port>`:开启 metrics 并设置 metrics 端口,对应 `metrics_bind_addr`
321320

322-
### HTTPS、域名和 QUIC
321+
### HTTPS 和域名
323322

324-
这些全是进阶配置,不是 `ii relay` 的前置条件。先创建并编辑配置文件,再启动
323+
HTTPS 是显式进阶模式,必须同时给域名、完整证书链和私钥
325324

326325
```powershell
327-
ii relay --config .\relay.toml
326+
ii relay --tls relay.example.com -H 8443 --cert .\fullchain.pem --key .\privkey.pem
328327
```
329328

330-
Cloudflare、Nginx 或其他反向代理已经占用 `80/443` 时,建议让它负责公网 TLS,`ii relay` 监听后端端口。ACME 由 `ii relay` 自己签发时,域名必须解析到该机器,并且验证请求必须能到达它。
331-
332-
一个 ACME + QUIC 示例:
329+
域名必须已经解析到 relay 服务器;`--cert` 指向 PEM 格式的完整证书链,`--key` 指向匹配的 PEM 私钥。证书的 SAN 必须包含 `--tls` 指定的域名。`ii` 不申请、不续期证书,也不要求联系邮箱。
333330

334-
```toml
335-
http_bind_addr = "0.0.0.0:8080"
336-
enable_quic_addr_discovery = true
331+
客户端使用 HTTPS:
337332

338-
[tls]
339-
https_bind_addr = "0.0.0.0:8443"
340-
quic_bind_addr = "0.0.0.0:7843"
341-
cert_mode = "LetsEncrypt"
342-
hostname = ["relay.example.com"]
343-
contact = "ops@example.com"
344-
prod_tls = true
333+
```powershell
334+
ii send .\video.mp4 --relay https://relay.example.com:8443
345335
```
346336

347-
启动时可覆盖端口
337+
TLS 模式不会在公网开放 HTTP relay。底层所需的 HTTP 探测监听只绑定本机随机端口,因此下面的地址不成立
348338

349339
```powershell
350-
ii relay --config .\relay.toml -S 8443 -Q 7843
340+
ii send .\video.mp4 --relay http://relay.example.com:8443
351341
```
352342

353-
客户端
343+
`--config` 也可保存手工 TLS 配置
354344

355-
```powershell
356-
ii send .\video.mp4 --relay https://relay.example.com:8443
345+
```toml
346+
[tls]
347+
domain = "relay.example.com"
348+
https_bind_addr = "0.0.0.0:8443"
349+
cert_path = "/etc/ii/fullchain.pem"
350+
key_path = "/etc/ii/privkey.pem"
357351
```
358352

359353
端口职责:
360354

361355
- `3340/tcp`:默认 HTTP relay
362-
- `443/tcp` 或自定义 TCP 端口:启用 `[tls]` 后的 HTTPS relay
363-
- `7842/udp` 或自定义 UDP 端口:启用 `[tls]` 后的 QUIC 地址发现
364-
- `9090/tcp`:metrics,默认关闭
356+
- `443/tcp``-H` 指定的 TCP 端口:启用 `--tls` 后的 HTTPS relay
357+
- `9090/tcp`:metrics,默认关闭;官方 release 当前未启用 metrics,传 `-M` 会报错
365358

366359
- 反向代理必须支持 WebSocket / HTTP upgrade。
367-
- `7842/udp` 不能由普通 HTTP 反向代理代替;不需要 QUIC 就保持 `enable_quic_addr_discovery = false`
368-
- `--https``--quic` 不会替你猜域名或证书;没有 `[tls]` 时会明确报错。
360+
- `--cert``--key` 缺任意一个都会拒绝启动。
369361

370362
### TLS 来源
371363

372-
显式配置 `[tls]` 后,HTTPS 可使用 ACME 自动签发、手工证书或文件重载。ACME 需要公网 IP、已解析到 relay 的 DNS 名和联系邮箱;IP 地址本身不能用于 LetsEncrypt 签发。
373-
374-
可调项:
375-
376-
- `IROH_RELAY_ACME_URL`:覆盖 ACME directory URL
377-
- `IROH_RELAY_ACME_CA`:给本地 ACME 测试服务器额外信任根证书
378-
379-
手工证书模式读取:
380-
381-
- `default.crt`
382-
- `default.key`
383-
384-
也可以显式覆盖:
385-
386-
- `manual_cert_path`
387-
- `manual_key_path`
388-
389-
还有 `Reloading` 模式,用于按证书文件重载。
364+
TLS 只读取用户显式指定的 `--cert``--key`,或 `relay.toml` 里的 `cert_path``key_path`。ACME、LetsEncrypt 和证书自动续期不属于 `ii`
390365

391366
## `ii doctor`
392367

src/cli.rs

Lines changed: 83 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,11 @@ pub struct RecvArgs {
4848
#[derive(Debug, Clone, Default)]
4949
pub struct RelayArgs {
5050
pub config: Option<PathBuf>,
51-
pub http: Option<u16>,
52-
pub https: Option<u16>,
53-
pub quic: Option<u16>,
51+
pub port: Option<u16>,
5452
pub metrics: Option<u16>,
53+
pub tls_domain: Option<String>,
54+
pub cert: Option<PathBuf>,
55+
pub key: Option<PathBuf>,
5556
}
5657

5758
impl Cli {
@@ -248,25 +249,32 @@ fn parse_relay(args: Vec<String>) -> Result<RelayArgs, ParseAction> {
248249
while let Some(arg) = iter.next() {
249250
match split_long_value(&arg) {
250251
Some(("config", value)) => out.config = Some(PathBuf::from(value)),
251-
Some(("http", value)) => out.http = Some(parse_port("--http", value)?),
252-
Some(("https", value)) => out.https = Some(parse_port("--https", value)?),
253-
Some(("quic", value)) => out.quic = Some(parse_port("--quic", value)?),
252+
Some(("port", value)) | Some(("http", value)) => {
253+
out.port = Some(parse_port("--port", value)?)
254+
}
254255
Some(("metrics", value)) => out.metrics = Some(parse_port("--metrics", value)?),
256+
Some(("tls", value)) => out.tls_domain = Some(value.to_string()),
257+
Some(("cert", value)) => out.cert = Some(PathBuf::from(value)),
258+
Some(("key", value)) => out.key = Some(PathBuf::from(value)),
255259
Some((flag, _)) => {
256260
return Err(ParseAction::error(format!("unknown option `--{flag}`")));
257261
}
258262
None => match arg.as_str() {
259263
"-h" | "--help" => return Err(ParseAction::help(RELAY_HELP)),
260264
"-c" | "--config" => out.config = Some(PathBuf::from(iter.value(&arg)?)),
261-
"-H" | "--http" => out.http = Some(parse_port(&arg, &iter.value(&arg)?)?),
262-
"-S" | "--https" => out.https = Some(parse_port(&arg, &iter.value(&arg)?)?),
263-
"-Q" | "--quic" => out.quic = Some(parse_port(&arg, &iter.value(&arg)?)?),
265+
"-H" | "--port" | "--http" => {
266+
out.port = Some(parse_port(&arg, &iter.value(&arg)?)?)
267+
}
264268
"-M" | "--metrics" => out.metrics = Some(parse_port(&arg, &iter.value(&arg)?)?),
269+
"--tls" => out.tls_domain = Some(iter.value("--tls")?),
270+
"--cert" => out.cert = Some(PathBuf::from(iter.value("--cert")?)),
271+
"--key" => out.key = Some(PathBuf::from(iter.value("--key")?)),
265272
_ => return Err(ParseAction::error(format!("unexpected argument `{arg}`"))),
266273
},
267274
}
268275
}
269276

277+
validate_relay(&out)?;
270278
Ok(out)
271279
}
272280

@@ -295,6 +303,30 @@ fn validate_send(args: &SendArgs) -> Result<(), ParseAction> {
295303
Ok(())
296304
}
297305

306+
fn validate_relay(args: &RelayArgs) -> Result<(), ParseAction> {
307+
if let Some(domain) = &args.tls_domain {
308+
if domain.is_empty()
309+
|| domain.contains("://")
310+
|| domain.contains('/')
311+
|| domain.contains(':')
312+
{
313+
return Err(ParseAction::error(
314+
"--tls expects a bare DNS name such as relay.example.com",
315+
));
316+
}
317+
}
318+
319+
match (&args.tls_domain, &args.cert, &args.key) {
320+
(Some(_), Some(_), Some(_)) | (None, None, None) => Ok(()),
321+
(Some(_), _, _) => Err(ParseAction::error(
322+
"--tls requires both --cert <path> and --key <path>",
323+
)),
324+
(None, _, _) => Err(ParseAction::error(
325+
"--cert and --key require --tls <domain>",
326+
)),
327+
}
328+
}
329+
298330
fn reject_extra(command: &str, args: Vec<String>) -> Result<(), ParseAction> {
299331
if args.iter().any(|arg| is_help(arg)) {
300332
return Err(ParseAction::help(match command {
@@ -400,10 +432,11 @@ Usage:
400432
401433
Options:
402434
-c, --config <path>
403-
-H, --http <port>
404-
-S, --https <port>
405-
-Q, --quic <port>
435+
-H, --port <port>
406436
-M, --metrics <port>
437+
--tls <domain>
438+
--cert <path>
439+
--key <path>
407440
";
408441

409442
const DOCTOR_HELP: &str = "Usage:\n ii doctor";
@@ -492,18 +525,51 @@ mod tests {
492525
}
493526

494527
#[test]
495-
fn relay_accepts_short_ports() {
496-
let cli = Cli::parse_from(["ii", "relay", "-H", "8080", "-S", "8443", "-Q", "7843"]);
528+
fn relay_accepts_tls_with_manual_certificate_paths() {
529+
let cli = Cli::parse_from([
530+
"ii",
531+
"relay",
532+
"--tls",
533+
"relay.example.com",
534+
"-H",
535+
"8443",
536+
"--cert",
537+
"fullchain.pem",
538+
"--key",
539+
"privkey.pem",
540+
]);
497541
match cli.command {
498542
Command::Relay(args) => {
499-
assert_eq!(args.http, Some(8080));
500-
assert_eq!(args.https, Some(8443));
501-
assert_eq!(args.quic, Some(7843));
543+
assert_eq!(args.port, Some(8443));
544+
assert_eq!(args.tls_domain.as_deref(), Some("relay.example.com"));
545+
assert_eq!(args.cert, Some(PathBuf::from("fullchain.pem")));
546+
assert_eq!(args.key, Some(PathBuf::from("privkey.pem")));
502547
}
503548
_ => panic!("expected relay command"),
504549
}
505550
}
506551

552+
#[test]
553+
fn relay_rejects_incomplete_tls_arguments() {
554+
let result = parse_args(["ii", "relay", "--tls", "relay.example.com"]);
555+
assert!(matches!(result, Err(ParseAction::Print { code: 2, .. })));
556+
}
557+
558+
#[test]
559+
fn relay_rejects_tls_url_instead_of_domain() {
560+
let result = parse_args([
561+
"ii",
562+
"relay",
563+
"--tls",
564+
"https://relay.example.com",
565+
"--cert",
566+
"fullchain.pem",
567+
"--key",
568+
"privkey.pem",
569+
]);
570+
assert!(matches!(result, Err(ParseAction::Print { code: 2, .. })));
571+
}
572+
507573
#[test]
508574
fn relay_rejects_removed_dev_mode() {
509575
let result = parse_args(["ii", "relay", "--dev"]);

0 commit comments

Comments
 (0)