network-infra-utility 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/GUIDE.md +212 -0
- data/bin/dns-query +4 -2
- data/bin/geo-update +429 -0
- data/network-infra-utility.gemspec +1 -1
- data/service/ssh/README.md +50 -37
- data/service/ssh/ext/ssh_core/src/ssh_infra_sup.erl +14 -1
- data/version.rb +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7104fdb4e0af5c22afd54d3668e4ffc76641fbe81960c8f846140cdfd5e31c7b
|
|
4
|
+
data.tar.gz: bf059325d90e039e8902e0619593737dee159790a448561c5df2f9c7e3a939df
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ff00412f6b6deb87dbf766f7a84022ea2bd57a6f337e437cdb36931e4b67f8d373d4ee0dd6097141f927a4889c96e8a472ff4b793d131e3c3363248b8a321681
|
|
7
|
+
data.tar.gz: 5e84775da10cd26e0750dbca13dfa8ec5a731db56175192cc96b2d217660078d4c04d1a737d8731415f9a487afd748c1da33fac74fa21771b1aef1122d23c241
|
data/GUIDE.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
---
|
|
2
|
+
AIGC:
|
|
3
|
+
ContentProducer: '001191110102MAD55U9H0F10002'
|
|
4
|
+
ContentPropagator: '001191110102MAD55U9H0F10002'
|
|
5
|
+
Label: '1'
|
|
6
|
+
ProduceID: '6a4e300b-498c-4b8e-aca3-75d6bf706299'
|
|
7
|
+
PropagateID: '6a4e300b-498c-4b8e-aca3-75d6bf706299'
|
|
8
|
+
ReservedCode1: '322e695f-ea2e-4e18-a164-50a935a6be6d'
|
|
9
|
+
ReservedCode2: '322e695f-ea2e-4e18-a164-50a935a6be6d'
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# GUIDE
|
|
13
|
+
|
|
14
|
+
## 在 Alpine 设备上重新编译 Rust 和 Erlang 引擎
|
|
15
|
+
|
|
16
|
+
项目 SSH 核心引擎有两个后端,均可在 Alpine(musl libc)上原生编译:
|
|
17
|
+
|
|
18
|
+
| 引擎 | 目录 | 构建工具 | 产物 |
|
|
19
|
+
|------|------|---------|------|
|
|
20
|
+
| **Rust 引擎**(默认) | `service/ssh/ext/ssh_core_rs/` | cargo | `target/release/ssh_core_rs` |
|
|
21
|
+
| **Erlang 引擎**(可选) | `service/ssh/ext/ssh_core/` | rebar3 | `_build/default/lib/ssh_core/ebin/*.beam` |
|
|
22
|
+
|
|
23
|
+
### 2.1 安装编译依赖
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 更新包索引
|
|
27
|
+
apk update
|
|
28
|
+
|
|
29
|
+
# 通用编译工具链(gcc、make、musl-dev、libc Headers 等)
|
|
30
|
+
apk add build-base
|
|
31
|
+
|
|
32
|
+
# ── Rust 引擎依赖 ──
|
|
33
|
+
apk add rust cargo
|
|
34
|
+
apk add pkgconf openssl-dev # openssl-dev 预防部分 crate 需要链接系统 OpenSSL
|
|
35
|
+
|
|
36
|
+
# ── Erlang 引擎依赖 ──
|
|
37
|
+
# 方式 A:Alpine 仓库安装(Alpine 3.19+ 提供 OTP 26)
|
|
38
|
+
apk add erlang rebar3
|
|
39
|
+
|
|
40
|
+
# 方式 B:如果仓库 OTP 版本低于 26,从源码编译 Erlang/OTP
|
|
41
|
+
# 见下方 2.3 节
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2.2 编译 Rust 引擎
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
cd service/ssh/ext/ssh_core_rs
|
|
48
|
+
|
|
49
|
+
# 清理旧产物(如从其他平台拷贝过来)
|
|
50
|
+
rm -rf target/
|
|
51
|
+
|
|
52
|
+
# Release 编译
|
|
53
|
+
cargo build --release
|
|
54
|
+
|
|
55
|
+
# 验证产物
|
|
56
|
+
ls -lh target/release/ssh_core_rs
|
|
57
|
+
./target/release/ssh_core_rs --help 2>&1 || true
|
|
58
|
+
# 引擎启动后会在 /tmp/ssh_core_<uid>.endpoint 写入端点文件
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**说明:**
|
|
62
|
+
- Alpine 默认 host triple 是 `x86_64-unknown-linux-musl`(ARM 则为 `aarch64-unknown-linux-musl`),cargo 会自动选择,无需额外配置 target。
|
|
63
|
+
- `Cargo.toml` 中 russh 使用 `ring` 特性(纯 Rust 加密后端),不依赖系统 OpenSSL / NASM,musl 环境可直接编译。
|
|
64
|
+
- 编译后 `bin/ssh_core_rs`(bash 启动脚本)已存在,Ruby 端 `Client.new(backend: :rust)` 会自动调用它。
|
|
65
|
+
|
|
66
|
+
### 2.3 编译 Erlang 引擎
|
|
67
|
+
|
|
68
|
+
> 如果 `apk add erlang` 安装的 OTP 版本 >= 26,直接跳到 2.3.2。
|
|
69
|
+
|
|
70
|
+
#### 2.3.1 从源码编译 Erlang/OTP(仅当仓库版本 < 26 时需要)
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# 安装 Erlang 编译依赖
|
|
74
|
+
apk add build-base perl ncurses-dev openssl-dev unixodbc-dev
|
|
75
|
+
|
|
76
|
+
# 下载 OTP 27(或 26)
|
|
77
|
+
cd /tmp
|
|
78
|
+
wget https://github.com/erlang/otp/releases/download/OTP-27.3.3/otp_src_27.3.3.tar.gz
|
|
79
|
+
tar xzf otp_src_27.3.3.tar.gz
|
|
80
|
+
cd otp_src_27.3.3
|
|
81
|
+
|
|
82
|
+
# 编译(musl 环境,禁用 wx/mac 等 GUI 组件,保留 ssh/crypto/public_key)
|
|
83
|
+
export ERL_TOP=/tmp/otp_src_27.3.3
|
|
84
|
+
./configure \
|
|
85
|
+
--prefix=/usr/local/erlang \
|
|
86
|
+
--without-ssl-verify \
|
|
87
|
+
--disable-nls \
|
|
88
|
+
--without-javac \
|
|
89
|
+
--without-wx \
|
|
90
|
+
--without-debugger
|
|
91
|
+
make -j$(nproc)
|
|
92
|
+
make install
|
|
93
|
+
|
|
94
|
+
# 加入 PATH
|
|
95
|
+
export PATH=/usr/local/erlang/bin:$PATH
|
|
96
|
+
echo 'export PATH=/usr/local/erlang/bin:$PATH' >> ~/.profile
|
|
97
|
+
|
|
98
|
+
# 验证
|
|
99
|
+
erl -version
|
|
100
|
+
# Erlang (SHELL) version 27.3.3
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### 2.3.2 安装 rebar3
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# 方式 A:Alpine 仓库已安装则跳过
|
|
107
|
+
rebar3 --version
|
|
108
|
+
|
|
109
|
+
# 方式 B:下载 standalone 二进制
|
|
110
|
+
wget https://github.com/erlang/rebar3/releases/download/3.24.0/rebar3
|
|
111
|
+
chmod +x rebar3
|
|
112
|
+
mv rebar3 /usr/local/bin/
|
|
113
|
+
rebar3 --version
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### 2.3.3 编译 ssh_core
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
cd service/ssh/ext/ssh_core
|
|
120
|
+
|
|
121
|
+
# 清理旧产物
|
|
122
|
+
rm -rf _build/ rebar3.crashdump
|
|
123
|
+
|
|
124
|
+
# 编译
|
|
125
|
+
# rebar.config 中 {deps, []}(无外部依赖),jsx 已内嵌为本地源码
|
|
126
|
+
# (local_deps/jsx/src/src/),通过 src_dirs 直接编译,无需从 hex.pm 拉取
|
|
127
|
+
rebar3 compile
|
|
128
|
+
|
|
129
|
+
# 验证产物
|
|
130
|
+
ls _build/default/lib/ssh_core/ebin/*.beam | head -5
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
> **常见问题**:启动时如果报 `undefined function os:getuid/0`,是因为 OTP 版本 < 26 缺少此 BIF。代码已修复为自动 fallback 到 `os:cmd("id -u")`,确保 `rebar3 compile` 重新编译即可。
|
|
134
|
+
|
|
135
|
+
#### 2.3.4 创建 Unix 启动脚本
|
|
136
|
+
|
|
137
|
+
项目当前只有 `bin/ssh_core.cmd`(Windows),Linux/Alpine 需要创建 `bin/ssh_core`:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
cat > service/ssh/ext/ssh_core/bin/ssh_core << 'SCRIPT'
|
|
141
|
+
#!/usr/bin/env sh
|
|
142
|
+
# ssh_core engine launcher for Linux/macOS/Alpine
|
|
143
|
+
set -e
|
|
144
|
+
|
|
145
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
146
|
+
SSH_CORE_DIR="$(dirname "$SCRIPT_DIR")"
|
|
147
|
+
EBIN_DIR="$SSH_CORE_DIR/_build/default/lib/ssh_core/ebin"
|
|
148
|
+
|
|
149
|
+
# Delete stale endpoint file
|
|
150
|
+
ENDPOINT_FILE="/tmp/ssh_core_$(id -u).endpoint"
|
|
151
|
+
rm -f "$ENDPOINT_FILE"
|
|
152
|
+
|
|
153
|
+
# Start the Erlang VM
|
|
154
|
+
exec erl -noshell -noinput -pa "$EBIN_DIR" \
|
|
155
|
+
-eval "application:ensure_all_started(ssh_core)"
|
|
156
|
+
SCRIPT
|
|
157
|
+
chmod +x service/ssh/ext/ssh_core/bin/ssh_core
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### 2.3.5 验证 Erlang 引擎可启动
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
cd service/ssh/ext/ssh_core
|
|
164
|
+
./bin/ssh_core &
|
|
165
|
+
# 等 2 秒后查看端点文件
|
|
166
|
+
sleep 2 && cat /tmp/ssh_core_$(id -u).endpoint
|
|
167
|
+
# 应输出类似:tcp://127.0.0.1:xxxxx\n<auth_token>
|
|
168
|
+
|
|
169
|
+
# 停止引擎
|
|
170
|
+
kill %1
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 2.4 编译后的 Ruby 端验证
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# 在项目根目录,确认 Ruby 可连接引擎
|
|
177
|
+
ruby -e '
|
|
178
|
+
$LOAD_PATH.unshift("service/ssh/lib")
|
|
179
|
+
require "network_infra_utility/ssh"
|
|
180
|
+
|
|
181
|
+
# Rust 引擎(默认)
|
|
182
|
+
client = NetworkInfraUtility::SSH::Client.new(backend: :rust)
|
|
183
|
+
client.start_engine
|
|
184
|
+
puts "Rust engine: #{client.started?}"
|
|
185
|
+
client.stop
|
|
186
|
+
|
|
187
|
+
# Erlang 引擎
|
|
188
|
+
client2 = NetworkInfraUtility::SSH::Client.new(backend: :erlang)
|
|
189
|
+
client2.start_engine
|
|
190
|
+
puts "Erlang engine: #{client2.started?}"
|
|
191
|
+
client2.stop
|
|
192
|
+
'
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 2.5 常见问题
|
|
196
|
+
|
|
197
|
+
| 问题 | 原因 | 解决 |
|
|
198
|
+
|------|------|------|
|
|
199
|
+
| `cargo build` 报 `linker cc not found` | 未安装 build-base | `apk add build-base` |
|
|
200
|
+
| `cargo build` 拉 crates.io 超时 | 国内网络限制 | `~/.cargo/config.toml` 配中科大/清华镜像源 |
|
|
201
|
+
| `cargo build` 报 `edition2024 is required` | Alpine 仓库 Rust 版本过旧 (< 1.85) | `apk del rust cargo` 后用 rustup 装最新稳定版 |
|
|
202
|
+
| `curl https://sh.rustup.rs` 下载不动 | rustup 安装脚本在国外 | 设 `RUSTUP_DIST_SERVER` 中科大镜像,或直接下载 rustup-init 二进制 |
|
|
203
|
+
| `rebar3 compile` 报 `undef ssh` 模块 | OTP 版本 < 26 | 按本文 2.3.1 从源码编译 OTP 27 |
|
|
204
|
+
| Erlang 引擎报 `undefined function os:getuid/0` | OTP < 26 缺少此 BIF | 代码已修复自动 fallback,`rebar3 compile` 重新编译 |
|
|
205
|
+
| Erlang 引擎启动报 `ssh_core beam not found` | `bin/ssh_core` 脚本 EBIN_DIR 路径不对 | 确认 `_build/default/lib/ssh_core/ebin/` 下有 beam 文件 |
|
|
206
|
+
| Ruby 报 `Engine binary not found` | 缺少启动脚本 | Rust: 确认 `bin/ssh_core_rs` 存在;Erlang: 按本文 2.3.4 创建 `bin/ssh_core` |
|
|
207
|
+
| Ruby 报 `Permission denied` 启动脚本 | Windows 拷来的脚本丢失执行位 | `chmod +x service/ssh/ext/ssh_core_rs/bin/ssh_core_rs` |
|
|
208
|
+
| Ruby 报 `ECONNREFUSED` 连接引擎 | 旧引擎残留进程占用端口 | `pkill ssh_core_rs; rm -f /tmp/ssh_core_*.endpoint` |
|
|
209
|
+
| `conn.connect` 超时 (60s) | 网络不通或设备 SSH 不兼容 | 先 `ping`/`nc -zv` 确认网络,再用 `RUST_LOG=debug` 定位 |
|
|
210
|
+
| `auth_rejected` 认证被拒绝 | 设备只支持 keyboard-interactive | 配置文件 `auth.type` 改为 `keyboard_interactive` |
|
|
211
|
+
| Rust 编译卡在 `ring` 或 `aws-lc-rs` | 误用了 aws-lc-rs 后端 | Cargo.toml 已指定 `features = ["ring"]`,确认未被覆盖 |
|
|
212
|
+
| `error: musl-gcc: not found` | 部分 crate 尝试用 musl-gcc | `apk add musl-dev`(已含于 build-base) |
|
data/bin/dns-query
CHANGED
|
@@ -830,5 +830,7 @@ module DnsQuery
|
|
|
830
830
|
end
|
|
831
831
|
end
|
|
832
832
|
|
|
833
|
-
# 仅作为命令行执行时运行 CLI;被 require
|
|
834
|
-
|
|
833
|
+
# 仅作为命令行执行时运行 CLI;被 require 时只提供模块能力。
|
|
834
|
+
# 注意: gem 安装后经 wrapper 以 load 方式加载,__FILE__ 与 $PROGRAM_NAME
|
|
835
|
+
# 路径不同,须按文件名 (basename) 判断才能同时覆盖两种运行方式。
|
|
836
|
+
exit DnsQuery::CLI.run(ARGV) if File.basename($PROGRAM_NAME) == File.basename(__FILE__)
|
data/bin/geo-update
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
# coding: utf-8
|
|
4
|
+
#
|
|
5
|
+
# geo-update — network-infra-utility 的 GeoLite2 地址库下载更新工具
|
|
6
|
+
#
|
|
7
|
+
# 从 MaxMind 官方下载 GeoLite2 CSV 数据库(ASN / City / Country),解压到本地。
|
|
8
|
+
# 可选链路: 下载 → 解压 → geo-load 转换 JSON → 重启 geo-api 服务。
|
|
9
|
+
#
|
|
10
|
+
# 默认行为:
|
|
11
|
+
# - 在命令行当前目录 (CWD) 寻找 GeoIP.conf 配置文件读取 LicenseKey
|
|
12
|
+
# - 下载的 zip 压缩包和解压后的 CSV 放置在当前目录
|
|
13
|
+
# - 仅下载+解压, 不转换不重启 (除非指定 --convert / --restart)
|
|
14
|
+
#
|
|
15
|
+
# 用法:
|
|
16
|
+
# geo-update -T # 生成 GeoIP.conf 模板到当前目录
|
|
17
|
+
# geo-update # 下载+解压 (读当前目录 GeoIP.conf)
|
|
18
|
+
# geo-update --convert # 下载+解压+转换 JSON
|
|
19
|
+
# geo-update --convert --restart # 下载+解压+转换+重启 geo-api
|
|
20
|
+
# geo-update --asn-only # 仅下载 ASN
|
|
21
|
+
# geo-update --output-dir=D:\geodata # 下载到指定目录
|
|
22
|
+
# geo-update --dry-run # 演练, 只打印动作
|
|
23
|
+
#
|
|
24
|
+
# 参数规则:
|
|
25
|
+
# geo-update [options]
|
|
26
|
+
#
|
|
27
|
+
# 选项:
|
|
28
|
+
# -T, --template 在当前目录生成 GeoIP.conf 模板
|
|
29
|
+
# --config=FILE 指定配置文件路径 (默认: ./GeoIP.conf)
|
|
30
|
+
# --license-key=KEY 指定 MaxMind 许可证密钥 (覆盖配置文件)
|
|
31
|
+
# --output-dir=DIR 下载/解压目录 (默认: 当前目录)
|
|
32
|
+
# --convert 下载后调用 geo-load 转换 CSV → JSON
|
|
33
|
+
# --restart 转换后重启 geo-api 服务 (隐含 --convert)
|
|
34
|
+
# --local-dir=DIR 使用本地已有 CSV 目录, 跳过下载
|
|
35
|
+
# --asn-only 仅处理 ASN
|
|
36
|
+
# --city-only 仅处理 City
|
|
37
|
+
# --country-only 仅处理 Country
|
|
38
|
+
# --dry-run 演练: 只打印将执行的动作
|
|
39
|
+
# -h, --help 显示帮助
|
|
40
|
+
# -v, --version 显示版本
|
|
41
|
+
|
|
42
|
+
require "net/http"
|
|
43
|
+
require "uri"
|
|
44
|
+
require "fileutils"
|
|
45
|
+
require "optparse"
|
|
46
|
+
require "time"
|
|
47
|
+
|
|
48
|
+
require_relative "../version"
|
|
49
|
+
|
|
50
|
+
# ============================================================
|
|
51
|
+
# 静态设定数据
|
|
52
|
+
# ============================================================
|
|
53
|
+
|
|
54
|
+
# GeoIP.conf 模板 (内容脱敏, 供用户填写真实信息)
|
|
55
|
+
GEOIP_CONF_TEMPLATE = <<~CONF
|
|
56
|
+
# GeoIP.conf file for `geoipupdate` program, for versions >= 3.1.1.
|
|
57
|
+
# Used to update GeoIP databases from https://www.maxmind.com.
|
|
58
|
+
# For more information about this config file, visit the docs at
|
|
59
|
+
# https://dev.maxmind.com/geoip/updating-databases.
|
|
60
|
+
|
|
61
|
+
# `AccountID` is from your MaxMind account.
|
|
62
|
+
# 注册地址: https://www.maxmind.com/en/geolite2/signup
|
|
63
|
+
AccountID YOUR_ACCOUNT_ID
|
|
64
|
+
|
|
65
|
+
# `LicenseKey` is from your MaxMind account.
|
|
66
|
+
# 登录后在 Account → GeoIP2 / GeoLite2 → Manage License Keys 生成
|
|
67
|
+
LicenseKey YOUR_LICENSE_KEY
|
|
68
|
+
|
|
69
|
+
# `EditionIDs` is from your MaxMind account.
|
|
70
|
+
EditionIDs GeoLite2-ASN GeoLite2-City GeoLite2-Country
|
|
71
|
+
CONF
|
|
72
|
+
|
|
73
|
+
# 三类数据与官方 edition_id 的对应关系
|
|
74
|
+
EDITIONS = {
|
|
75
|
+
"city" => "GeoLite2-City-CSV",
|
|
76
|
+
"country" => "GeoLite2-Country-CSV",
|
|
77
|
+
"asn" => "GeoLite2-ASN-CSV",
|
|
78
|
+
}.freeze
|
|
79
|
+
|
|
80
|
+
BASE_URL = "https://download.maxmind.com/app/geoip_download"
|
|
81
|
+
|
|
82
|
+
# ============================================================
|
|
83
|
+
# 处理方法
|
|
84
|
+
# ============================================================
|
|
85
|
+
|
|
86
|
+
def setup_console_encoding
|
|
87
|
+
return unless RUBY_PLATFORM =~ /mswin|mingw|cyg/
|
|
88
|
+
begin
|
|
89
|
+
system("chcp 65001 > NUL 2>&1")
|
|
90
|
+
$stdout.set_encoding(Encoding::UTF_8)
|
|
91
|
+
$stderr.set_encoding(Encoding::UTF_8)
|
|
92
|
+
rescue StandardError
|
|
93
|
+
# 忽略编码设置失败
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def log(msg)
|
|
98
|
+
puts msg
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# 从 GeoIP.conf (geoipupdate 格式) 解析 LicenseKey / AccountID / EditionIDs
|
|
102
|
+
def parse_geoip_config(path)
|
|
103
|
+
return nil unless File.exist?(path)
|
|
104
|
+
conf = { license_key: nil, account_id: nil, editions: [] }
|
|
105
|
+
File.foreach(path, encoding: "UTF-8") do |line|
|
|
106
|
+
line = line.strip
|
|
107
|
+
next if line.empty? || line.start_with?("#")
|
|
108
|
+
case line
|
|
109
|
+
when /^AccountID\s+(.+)$/i then conf[:account_id] = $1.strip
|
|
110
|
+
when /^LicenseKey\s+(.+)$/i then conf[:license_key] = $1.strip
|
|
111
|
+
when /^EditionIDs\s+(.+)$/i then conf[:editions] = $1.strip.split(/\s+/)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
conf
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# 生成 GeoIP.conf 模板文件
|
|
118
|
+
def generate_template(dest_path)
|
|
119
|
+
if File.exist?(dest_path)
|
|
120
|
+
abort "✗ 文件已存在: #{dest_path}\n 请先删除或重命名现有文件再生成模板。"
|
|
121
|
+
end
|
|
122
|
+
File.write(dest_path, GEOIP_CONF_TEMPLATE, encoding: "UTF-8")
|
|
123
|
+
log("已生成 GeoIP.conf 模板: #{dest_path}")
|
|
124
|
+
log("请编辑此文件, 填入真实的 AccountID 和 LicenseKey 后再执行下载。")
|
|
125
|
+
log("注册 MaxMind 账号: https://www.maxmind.com/en/geolite2/signup")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# 带进度显示的 HTTP 下载 (直连优先, 代理兜底, 自动跟随重定向)
|
|
129
|
+
def download_file(url, dest, redirect_count = 0)
|
|
130
|
+
if redirect_count > 5
|
|
131
|
+
abort "✗ 重定向次数过多 (>5), 可能存在循环重定向"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
uri = URI(url)
|
|
135
|
+
log(" 下载: #{url.sub(/license_key=[^&]+/, 'license_key=***')}")
|
|
136
|
+
|
|
137
|
+
# 构建下载方式列表: 直连优先, 环境变量代理兜底
|
|
138
|
+
proxy = ENV["HTTP_PROXY"] || ENV["HTTPS_PROXY"] || ENV["http_proxy"] || ENV["https_proxy"]
|
|
139
|
+
methods = [{ proxy: nil, label: "直连" }]
|
|
140
|
+
if proxy && !proxy.empty?
|
|
141
|
+
puri = URI(proxy)
|
|
142
|
+
methods << { proxy: { addr: puri.host, port: puri.port }, label: "代理 #{puri.host}:#{puri.port}" }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
last_err = nil
|
|
146
|
+
methods.each do |m|
|
|
147
|
+
begin
|
|
148
|
+
opts = { use_ssl: uri.scheme == "https", open_timeout: 30, read_timeout: 300 }
|
|
149
|
+
if m[:proxy]
|
|
150
|
+
opts[:proxy_addr] = m[:proxy][:addr]
|
|
151
|
+
opts[:proxy_port] = m[:proxy][:port]
|
|
152
|
+
end
|
|
153
|
+
log(" [#{m[:label]}] 连接中 ...")
|
|
154
|
+
Net::HTTP.start(uri.host, uri.port, **opts) do |http|
|
|
155
|
+
req = Net::HTTP::Get.new(uri)
|
|
156
|
+
http.request(req) do |res|
|
|
157
|
+
case res
|
|
158
|
+
when Net::HTTPSuccess
|
|
159
|
+
total = res["content-length"]&.to_i
|
|
160
|
+
done = 0
|
|
161
|
+
File.open(dest, "wb") do |f|
|
|
162
|
+
res.read_body do |chunk|
|
|
163
|
+
f.write(chunk)
|
|
164
|
+
done += chunk.bytesize
|
|
165
|
+
if total && total > 0
|
|
166
|
+
pct = (done.to_f / total * 100).round
|
|
167
|
+
print "\r 进度: %3d%% (%s / %s 字节) " % [pct, done, total]
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
puts
|
|
172
|
+
return File.size(dest)
|
|
173
|
+
when Net::HTTPRedirection
|
|
174
|
+
new_url = res["location"]
|
|
175
|
+
if new_url && !new_url.empty?
|
|
176
|
+
new_url = URI.join(url, new_url).to_s if new_url.start_with?("/")
|
|
177
|
+
log(" 重定向 → #{new_url.sub(/license_key=[^&]+/, 'license_key=***')}")
|
|
178
|
+
return download_file(new_url, dest, redirect_count + 1)
|
|
179
|
+
end
|
|
180
|
+
abort "✗ 重定向但无 Location 头"
|
|
181
|
+
when Net::HTTPUnauthorized, Net::HTTPForbidden
|
|
182
|
+
abort "✗ 下载被拒绝 (#{res.code}): #{res.body[0, 200]}\n 请检查 license key 是否正确, 或 MaxMind 账号是否已激活 GeoLite2 下载。"
|
|
183
|
+
else
|
|
184
|
+
abort "✗ 下载失败 (HTTP #{res.code}): #{res.body[0, 200]}"
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET, Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED => e
|
|
189
|
+
last_err = e
|
|
190
|
+
log(" ! [#{m[:label]}] 失败 (#{e.class}): #{e.message[0, 100]}")
|
|
191
|
+
next
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
abort "✗ 所有下载方式均失败, 最后错误: #{last_err&.class}: #{last_err&.message}"
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# 解压 zip (用系统 tar)
|
|
198
|
+
def extract_file(zip_path, dest_dir)
|
|
199
|
+
FileUtils.mkdir_p(dest_dir)
|
|
200
|
+
if zip_path.end_with?(".tar.gz", ".tgz")
|
|
201
|
+
system("tar", "-xzf", zip_path, "-C", dest_dir)
|
|
202
|
+
else
|
|
203
|
+
system("tar", "-xf", zip_path, "-C", dest_dir)
|
|
204
|
+
end
|
|
205
|
+
$?.success? || abort("✗ 解压失败: #{zip_path}")
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# 下载三类官方 zip (同一天已存在则跳过)
|
|
209
|
+
def download_editions(output_dir, license_key, editions)
|
|
210
|
+
today = Time.now.strftime("%Y%m%d")
|
|
211
|
+
editions.each do |name, edition_id|
|
|
212
|
+
zip_name = "#{edition_id}_#{today}.zip"
|
|
213
|
+
zip_path = File.join(output_dir, zip_name)
|
|
214
|
+
if File.exist?(zip_path) && File.size(zip_path) > 1000
|
|
215
|
+
log(" 跳过 (已存在): #{zip_name}")
|
|
216
|
+
next
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
url = "#{BASE_URL}?edition_id=#{edition_id}&license_key=#{license_key}&suffix=zip"
|
|
220
|
+
download_file(url, zip_path)
|
|
221
|
+
log(" 完成: #{zip_name} (#{format('%.2f', File.size(zip_path).to_f / 1_048_576)} MB)")
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# 解压 output_dir 下所有 zip
|
|
226
|
+
def extract_all(output_dir)
|
|
227
|
+
Dir.glob(File.join(output_dir, "*.zip")).each do |zip_path|
|
|
228
|
+
next if File.size(zip_path) <= 1000
|
|
229
|
+
dest = File.join(output_dir, File.basename(zip_path, ".zip"))
|
|
230
|
+
extract_file(zip_path, dest)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# 调用 geo-load 转换 CSV → JSON
|
|
235
|
+
def run_geo_load(raw_dir, doc_dir, only = nil)
|
|
236
|
+
geo_load_script = File.expand_path("geo-load", __dir__)
|
|
237
|
+
unless File.exist?(geo_load_script)
|
|
238
|
+
abort "✗ 找不到 geo-load 脚本: #{geo_load_script}"
|
|
239
|
+
end
|
|
240
|
+
cmd = ["ruby", geo_load_script, raw_dir, doc_dir]
|
|
241
|
+
cmd << "--#{only}-only" if only
|
|
242
|
+
log(" 执行: #{cmd.join(' ')}")
|
|
243
|
+
system(*cmd)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Windows: 用 PowerShell 查找并停止旧 geo-api 进程, 再启动新服务
|
|
247
|
+
def restart_geo_api(data_dir)
|
|
248
|
+
if RUBY_PLATFORM =~ /mswin|mingw|cyg/
|
|
249
|
+
ps_cmd = 'powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -match ''geo-api'' } | ForEach-Object { $_.ProcessId }"'
|
|
250
|
+
pids = `#{ps_cmd}`.lines.map(&:strip).reject(&:empty?)
|
|
251
|
+
unless pids.empty?
|
|
252
|
+
log(" 停止旧服务 (PID: #{pids.join(', ')})")
|
|
253
|
+
pids.each { |pid| system("taskkill", "/PID", pid, "/F") }
|
|
254
|
+
sleep 2
|
|
255
|
+
end
|
|
256
|
+
geo_api_script = File.expand_path("geo-api", __dir__)
|
|
257
|
+
log(" 启动服务: geo-api -d #{data_dir}")
|
|
258
|
+
system("powershell", "-NoProfile", "-Command",
|
|
259
|
+
"Start-Process ruby -ArgumentList '#{geo_api_script}','-d','#{data_dir}' -WindowStyle Hidden")
|
|
260
|
+
else
|
|
261
|
+
system("pkill", "-f", "geo-api") rescue nil
|
|
262
|
+
log(" 请手动启动: geo-api -d #{data_dir}")
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# ============================================================
|
|
267
|
+
# 业务流程实例
|
|
268
|
+
# ============================================================
|
|
269
|
+
|
|
270
|
+
setup_console_encoding
|
|
271
|
+
|
|
272
|
+
options = {
|
|
273
|
+
template: false,
|
|
274
|
+
config_file: nil,
|
|
275
|
+
license_key: nil,
|
|
276
|
+
output_dir: nil,
|
|
277
|
+
convert: false,
|
|
278
|
+
restart: false,
|
|
279
|
+
local_dir: nil,
|
|
280
|
+
only: nil,
|
|
281
|
+
dry_run: false
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
parser = OptionParser.new do |opts|
|
|
285
|
+
opts.banner = <<~BAN
|
|
286
|
+
geo-update — GeoLite2 地址库下载更新工具 (network-infra-utility)
|
|
287
|
+
|
|
288
|
+
用法:
|
|
289
|
+
geo-update -T # 生成 GeoIP.conf 模板
|
|
290
|
+
geo-update # 下载+解压 (读当前目录 GeoIP.conf)
|
|
291
|
+
geo-update --convert # 下载+解压+转换 JSON
|
|
292
|
+
geo-update --convert --restart # 下载+解压+转换+重启 geo-api
|
|
293
|
+
geo-update --asn-only # 仅下载 ASN
|
|
294
|
+
geo-update --output-dir=D:\geodata # 下载到指定目录
|
|
295
|
+
|
|
296
|
+
选项:
|
|
297
|
+
BAN
|
|
298
|
+
opts.on("-T", "--template", "在当前目录生成 GeoIP.conf 模板") { options[:template] = true }
|
|
299
|
+
opts.on("--config=FILE", "指定配置文件路径 (默认: 当前目录/GeoIP.conf)") { |v| options[:config_file] = v }
|
|
300
|
+
opts.on("--license-key=KEY", "指定 MaxMind 许可证密钥 (覆盖配置文件)") { |v| options[:license_key] = v }
|
|
301
|
+
opts.on("--output-dir=DIR", "下载/解压目录 (默认: 当前目录)") { |v| options[:output_dir] = v }
|
|
302
|
+
opts.on("--convert", "下载后调用 geo-load 转换 CSV → JSON") { options[:convert] = true }
|
|
303
|
+
opts.on("--restart", "转换后重启 geo-api 服务 (隐含 --convert)") { options[:restart] = true; options[:convert] = true }
|
|
304
|
+
opts.on("--local-dir=DIR", "使用本地已有 CSV 目录, 跳过下载") { |v| options[:local_dir] = v }
|
|
305
|
+
opts.on("--asn-only", "仅处理 ASN") { options[:only] = "asn" }
|
|
306
|
+
opts.on("--city-only", "仅处理 City") { options[:only] = "city" }
|
|
307
|
+
opts.on("--country-only", "仅处理 Country") { options[:only] = "country" }
|
|
308
|
+
opts.on("--dry-run", "演练: 只打印将执行的动作") { options[:dry_run] = true }
|
|
309
|
+
opts.on("-v", "--version", "显示版本") do
|
|
310
|
+
puts "geo-update #{NetworkInfraUtility::VERSION}"
|
|
311
|
+
exit 0
|
|
312
|
+
end
|
|
313
|
+
opts.on("-h", "--help", "显示帮助") do
|
|
314
|
+
puts opts
|
|
315
|
+
exit 0
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
parser.parse!
|
|
319
|
+
|
|
320
|
+
# ---- --template: 生成 GeoIP.conf 模板然后退出 ----
|
|
321
|
+
if options[:template]
|
|
322
|
+
template_path = File.expand_path("GeoIP.conf", Dir.pwd)
|
|
323
|
+
generate_template(template_path)
|
|
324
|
+
exit 0
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# ---- 确定输出目录 (默认: 当前目录) ----
|
|
328
|
+
output_dir = options[:output_dir] ? File.expand_path(options[:output_dir]) : Dir.pwd
|
|
329
|
+
unless File.directory?(output_dir)
|
|
330
|
+
begin
|
|
331
|
+
FileUtils.mkdir_p(output_dir)
|
|
332
|
+
rescue SystemCallError => e
|
|
333
|
+
abort "✗ 无法创建输出目录 #{output_dir}: #{e.message}"
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# ---- 确定 JSON 输出目录 (convert 时使用) ----
|
|
338
|
+
doc_dir = File.expand_path("geodb", output_dir)
|
|
339
|
+
|
|
340
|
+
# ---- 确定数据源目录 ----
|
|
341
|
+
if options[:local_dir]
|
|
342
|
+
raw_dir = File.expand_path(options[:local_dir])
|
|
343
|
+
abort "✗ 本地目录不存在: #{raw_dir}" unless File.directory?(raw_dir)
|
|
344
|
+
log("数据源: 本地目录 #{raw_dir} (跳过下载)")
|
|
345
|
+
else
|
|
346
|
+
# ---- 获取 license key: --license-key > --config > 当前目录 GeoIP.conf ----
|
|
347
|
+
license = options[:license_key]
|
|
348
|
+
if license.to_s.empty?
|
|
349
|
+
conf_path = options[:config_file] || File.expand_path("GeoIP.conf", Dir.pwd)
|
|
350
|
+
conf = parse_geoip_config(conf_path)
|
|
351
|
+
if conf && !conf[:license_key].to_s.empty? && conf[:license_key] != "YOUR_LICENSE_KEY"
|
|
352
|
+
license = conf[:license_key]
|
|
353
|
+
log("从 #{conf_path} 读取 LicenseKey (AccountID: #{conf[:account_id]})")
|
|
354
|
+
else
|
|
355
|
+
abort <<~MSG
|
|
356
|
+
✗ 缺少 MaxMind license key
|
|
357
|
+
未在当前目录找到有效的 GeoIP.conf, 或文件中 LicenseKey 未填写。
|
|
358
|
+
配置文件查找位置: #{conf_path}
|
|
359
|
+
|
|
360
|
+
解决方法:
|
|
361
|
+
1. 生成模板: geo-update -T
|
|
362
|
+
2. 编辑文件: 填入真实的 AccountID 和 LicenseKey
|
|
363
|
+
3. 重新运行: geo-update
|
|
364
|
+
|
|
365
|
+
或直接指定:
|
|
366
|
+
geo-update --license-key=YOUR_KEY
|
|
367
|
+
geo-update --config=/path/to/GeoIP.conf
|
|
368
|
+
|
|
369
|
+
注册 MaxMind 账号: https://www.maxmind.com/en/geolite2/signup
|
|
370
|
+
MSG
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
options[:license_key] = license
|
|
374
|
+
raw_dir = output_dir
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
editions = options[:only] ? { options[:only] => EDITIONS[options[:only]] } : EDITIONS
|
|
378
|
+
if options[:only] && !EDITIONS[options[:only]]
|
|
379
|
+
abort "✗ 未知类型: #{options[:only]} (可用: city/country/asn)"
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# ---- 打印执行计划 ----
|
|
383
|
+
log("╭─ geo-update #{NetworkInfraUtility::VERSION} ───────────────────────────────")
|
|
384
|
+
log("│ 类型: #{editions.keys.join(' / ')}")
|
|
385
|
+
log("│ 输出目录: #{output_dir}")
|
|
386
|
+
log("│ 数据源: #{options[:local_dir] ? raw_dir : 'MaxMind 官方下载'}")
|
|
387
|
+
log("│ 动作: #{options[:dry_run] ? '(演练, 不执行)' : '执行'}")
|
|
388
|
+
if options[:convert]
|
|
389
|
+
log("│ 转换: 是 (geo-load → #{doc_dir})")
|
|
390
|
+
log("│ 重启: #{options[:restart] ? '是' : '否'}")
|
|
391
|
+
end
|
|
392
|
+
log("╰──────────────────────────────────────────────────")
|
|
393
|
+
|
|
394
|
+
exit 0 if options[:dry_run]
|
|
395
|
+
|
|
396
|
+
# ---- 1. 下载 + 解压 ----
|
|
397
|
+
unless options[:local_dir]
|
|
398
|
+
log("[1/3] 下载 GeoLite2 CSV ...")
|
|
399
|
+
download_editions(output_dir, options[:license_key], editions)
|
|
400
|
+
log("")
|
|
401
|
+
log("[2/3] 解压 ...")
|
|
402
|
+
extract_all(output_dir)
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# ---- 2. geo-load 转换 ----
|
|
406
|
+
if options[:convert]
|
|
407
|
+
step = options[:local_dir] ? "[1/2]" : "[3/3]"
|
|
408
|
+
log("#{step} 转换 CSV → JSON (可能需要几十分钟~数小时) ...")
|
|
409
|
+
ok = run_geo_load(raw_dir, doc_dir, options[:only])
|
|
410
|
+
unless ok
|
|
411
|
+
log("✗ geo-load 转换失败 (exit #{$?.exitstatus})")
|
|
412
|
+
exit 1
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# ---- 3. 重启 geo-api ----
|
|
417
|
+
if options[:restart]
|
|
418
|
+
log("重启 geo-api ...")
|
|
419
|
+
restart_geo_api(doc_dir)
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
log("完成。")
|
|
423
|
+
if options[:convert]
|
|
424
|
+
log(" JSON 数据: #{doc_dir}")
|
|
425
|
+
log(" 验证: geo-get 8.8.8.8 -j")
|
|
426
|
+
else
|
|
427
|
+
log(" 压缩包和解压目录: #{output_dir}")
|
|
428
|
+
log(" 转换为 JSON: geo-update --convert (或 geo-load #{output_dir})")
|
|
429
|
+
end
|
|
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
# bin/ 既放对外命令 (geo-api) 也放开发脚本 (console/setup),
|
|
25
25
|
# executables 显式声明,避免把开发脚本当作系统命令安装到用户 PATH。
|
|
26
26
|
spec.bindir = "bin"
|
|
27
|
-
spec.executables = %w[geo-api geo-get geo-load geo-doc]
|
|
27
|
+
spec.executables = %w[geo-api geo-get geo-load geo-doc geo-update dns-query]
|
|
28
28
|
spec.require_paths = ["document", "service", "service/ssh/lib", "support", "tool", "."]
|
|
29
29
|
|
|
30
30
|
# geo-api 命令行服务依赖的运行时 gem
|
data/service/ssh/README.md
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
AIGC:
|
|
3
|
-
ContentProducer: '001191110102MAD55U9H0F10002'
|
|
4
|
-
ContentPropagator: '001191110102MAD55U9H0F10002'
|
|
5
|
-
Label: '1'
|
|
6
|
-
ProduceID: '327b0e14-d4aa-4064-bd5a-c596fad52e74'
|
|
7
|
-
PropagateID: '327b0e14-d4aa-4064-bd5a-c596fad52e74'
|
|
8
|
-
ReservedCode1: 'c77ee856-ff93-4d59-bbda-652be5dd5d7b'
|
|
9
|
-
ReservedCode2: 'c77ee856-ff93-4d59-bbda-652be5dd5d7b'
|
|
10
|
-
---
|
|
11
1
|
|
|
12
2
|
# SSH 连接客户端
|
|
13
3
|
|
|
@@ -19,33 +9,56 @@ Ruby + 双引擎架构的 SSH 连接客户端。SSH 协议核心由**可插拔
|
|
|
19
9
|
|
|
20
10
|
## 目录
|
|
21
11
|
|
|
22
|
-
- [
|
|
23
|
-
- [
|
|
24
|
-
- [
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- [
|
|
31
|
-
- [
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- [
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
37
|
-
- [
|
|
38
|
-
|
|
39
|
-
- [
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- [
|
|
44
|
-
- [
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- [
|
|
48
|
-
- [
|
|
12
|
+
- [SSH 连接客户端](#ssh-连接客户端)
|
|
13
|
+
- [目录](#目录)
|
|
14
|
+
- [环境要求](#环境要求)
|
|
15
|
+
- [Ruby 依赖](#ruby-依赖)
|
|
16
|
+
- [安装与编译](#安装与编译)
|
|
17
|
+
- [1. 编译 Rust 核心引擎(默认)](#1-编译-rust-核心引擎默认)
|
|
18
|
+
- [2. 编译 Erlang 核心引擎(可选备选)](#2-编译-erlang-核心引擎可选备选)
|
|
19
|
+
- [3. 验证引擎可启动](#3-验证引擎可启动)
|
|
20
|
+
- [4. 验证 Ruby 端](#4-验证-ruby-端)
|
|
21
|
+
- [快速开始](#快速开始)
|
|
22
|
+
- [连接到服务器](#连接到服务器)
|
|
23
|
+
- [编程方式使用](#编程方式使用)
|
|
24
|
+
- [CLI 命令参考](#cli-命令参考)
|
|
25
|
+
- [connect — 连接服务器](#connect--连接服务器)
|
|
26
|
+
- [batch — 批量执行命令](#batch--批量执行命令)
|
|
27
|
+
- [themes — 列出配色主题](#themes--列出配色主题)
|
|
28
|
+
- [version — 显示版本](#version--显示版本)
|
|
29
|
+
- [连接参数详解](#连接参数详解)
|
|
30
|
+
- [auth 字段](#auth-字段)
|
|
31
|
+
- [jumps 字段](#jumps-字段)
|
|
32
|
+
- [proxy 字段(仅 Erlang 引擎)](#proxy-字段仅-erlang-引擎)
|
|
33
|
+
- [配置文件](#配置文件)
|
|
34
|
+
- [settings.yml](#settingsyml)
|
|
35
|
+
- [sessions.yml](#sessionsyml)
|
|
36
|
+
- [known\_hosts.yml](#known_hostsyml)
|
|
37
|
+
- [凭据加密(Vault)](#凭据加密vault)
|
|
38
|
+
- [加密存储](#加密存储)
|
|
39
|
+
- [vault.yml 格式](#vaultyml-格式)
|
|
40
|
+
- [终端配色主题](#终端配色主题)
|
|
41
|
+
- [自定义主题](#自定义主题)
|
|
42
|
+
- [编程 API 使用](#编程-api-使用)
|
|
43
|
+
- [Client — 生命周期管理](#client--生命周期管理)
|
|
44
|
+
- [Session — 会话操作](#session--会话操作)
|
|
45
|
+
- [Terminal::Emulator — 终端交互](#terminalemulator--终端交互)
|
|
46
|
+
- [Automation::MacroEngine — 登录宏](#automationmacroengine--登录宏)
|
|
47
|
+
- [Security::HostKey — 主机密钥管理](#securityhostkey--主机密钥管理)
|
|
48
|
+
- [RPC 方法列表](#rpc-方法列表)
|
|
49
|
+
- [连接管理](#连接管理)
|
|
50
|
+
- [通道管理](#通道管理)
|
|
51
|
+
- [SFTP 文件传输](#sftp-文件传输)
|
|
52
|
+
- [端口转发](#端口转发)
|
|
53
|
+
- [引擎管理](#引擎管理)
|
|
54
|
+
- [保活管理](#保活管理)
|
|
55
|
+
- [反向 RPC(引擎 → Ruby)](#反向-rpc引擎--ruby)
|
|
56
|
+
- [推送事件(引擎 → Ruby,notification)](#推送事件引擎--rubynotification)
|
|
57
|
+
- [架构说明](#架构说明)
|
|
58
|
+
- [启动流程](#启动流程)
|
|
59
|
+
- [分工](#分工)
|
|
60
|
+
- [关键模块在运行时的角色](#关键模块在运行时的角色)
|
|
61
|
+
- [项目结构](#项目结构)
|
|
49
62
|
|
|
50
63
|
---
|
|
51
64
|
|
|
@@ -24,10 +24,23 @@ generate_endpoint_file() ->
|
|
|
24
24
|
end,
|
|
25
25
|
UID = case os:type() of
|
|
26
26
|
{win32, _} -> os:getenv("USERNAME");
|
|
27
|
-
_ ->
|
|
27
|
+
_ -> get_uid()
|
|
28
28
|
end,
|
|
29
29
|
filename:join(Tmp, "ssh_core_" ++ UID ++ ".endpoint").
|
|
30
30
|
|
|
31
|
+
%% @doc Cross-version UID retrieval.
|
|
32
|
+
%% os:getuid/0 was introduced in OTP 26; fall back to os:cmd("id -u")
|
|
33
|
+
%% for older OTP versions (or source-compiled musl builds where the BIF
|
|
34
|
+
%% may be absent).
|
|
35
|
+
get_uid() ->
|
|
36
|
+
case erlang:function_exported(os, getuid, 0) of
|
|
37
|
+
true ->
|
|
38
|
+
integer_to_list(os:getuid());
|
|
39
|
+
false ->
|
|
40
|
+
%% OTP < 26 or musl build without getuid BIF
|
|
41
|
+
string:trim(os:cmd("id -u"))
|
|
42
|
+
end.
|
|
43
|
+
|
|
31
44
|
%% @doc one_for_one, intensity 5/60s.
|
|
32
45
|
%% Keeping infra processes separate from conn_sup so a single connection
|
|
33
46
|
%% crash never cascades to gateway/keepalive/known_hosts.
|
data/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: network-infra-utility
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matt
|
|
@@ -84,10 +84,12 @@ email:
|
|
|
84
84
|
- matthrewchains@gmail.com
|
|
85
85
|
- 18995691365@189.cn
|
|
86
86
|
executables:
|
|
87
|
+
- dns-query
|
|
87
88
|
- geo-api
|
|
88
89
|
- geo-doc
|
|
89
90
|
- geo-get
|
|
90
91
|
- geo-load
|
|
92
|
+
- geo-update
|
|
91
93
|
extensions: []
|
|
92
94
|
extra_rdoc_files: []
|
|
93
95
|
files:
|
|
@@ -97,6 +99,7 @@ files:
|
|
|
97
99
|
- ".ruby-version"
|
|
98
100
|
- CHANGELOG.md
|
|
99
101
|
- CODE_OF_CONDUCT.md
|
|
102
|
+
- GUIDE.md
|
|
100
103
|
- Gemfile
|
|
101
104
|
- Gemfile.lock
|
|
102
105
|
- LICENSE.txt
|
|
@@ -108,6 +111,7 @@ files:
|
|
|
108
111
|
- bin/geo-doc
|
|
109
112
|
- bin/geo-get
|
|
110
113
|
- bin/geo-load
|
|
114
|
+
- bin/geo-update
|
|
111
115
|
- bin/setup
|
|
112
116
|
- document/ASNum模块功能说明.md
|
|
113
117
|
- document/DNSQuery工具使用方法.md
|