@chenpu17/tcpmapping 2.0.1 → 2.1.1
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.
- package/README.md +96 -138
- package/bin/tcpmapping.js +131 -34
- package/package.json +2 -3
- package/server.js +83 -20
- package/setup-password.js +63 -12
- package/config.json +0 -46
package/README.md
CHANGED
|
@@ -1,227 +1,185 @@
|
|
|
1
1
|
# TcpMapping
|
|
2
2
|
|
|
3
|
-
TCP
|
|
3
|
+
TCP port forwarding server with WebUI management and real-time connection monitoring.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
TCP端口转发服务器,支持WebUI管理界面和实时连接监控。
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- **多端口映射** - 同时管理多个转发规则
|
|
9
|
-
- **WebUI管理** - 现代化Web界面,实时监控和管理
|
|
10
|
-
- **连接监控** - 实时显示每个映射的连接数和流量统计
|
|
11
|
-
- **安全认证** - bcrypt密码加密、登录失败保护、会话管理
|
|
7
|
+
---
|
|
12
8
|
|
|
13
|
-
##
|
|
9
|
+
## Features | 功能特性
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
- **TCP Port Forwarding** - Forward TCP traffic to backend servers | TCP端口转发
|
|
12
|
+
- **Multiple Mappings** - Manage multiple forwarding rules simultaneously | 多端口映射
|
|
13
|
+
- **WebUI Management** - Modern web interface for real-time monitoring | WebUI管理界面
|
|
14
|
+
- **Connection Monitoring** - Real-time connection count and traffic statistics | 实时连接监控
|
|
15
|
+
- **Secure Authentication** - bcrypt password hashing, login protection | 安全认证
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
# 全局安装
|
|
19
|
-
npm install -g @chenpu17/tcpmapping
|
|
17
|
+
---
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
tcpmapping init
|
|
19
|
+
## Installation | 安装
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
### NPM Global Install (Recommended) | NPM全局安装(推荐)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g @chenpu17/tcpmapping
|
|
26
25
|
|
|
27
|
-
#
|
|
28
|
-
tcpmapping
|
|
26
|
+
tcpmapping setup-password # Set admin password | 设置管理员密码
|
|
27
|
+
tcpmapping init # Create config file | 创建配置文件
|
|
28
|
+
tcpmapping start # Start server | 启动服务
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
###
|
|
31
|
+
### Local Install | 本地安装
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
# 克隆项目
|
|
35
34
|
git clone https://github.com/chenpu17/TcpMapping.git
|
|
36
35
|
cd TcpMapping
|
|
37
|
-
|
|
38
|
-
# 安装依赖
|
|
39
36
|
npm install
|
|
40
|
-
|
|
41
|
-
# 设置管理员密码
|
|
42
37
|
npm run setup-password
|
|
43
|
-
|
|
44
|
-
# 启动服务器
|
|
45
38
|
npm start
|
|
46
39
|
```
|
|
47
40
|
|
|
48
|
-
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## CLI Commands | CLI命令
|
|
44
|
+
|
|
45
|
+
| Command | Description |
|
|
46
|
+
|---------|-------------|
|
|
47
|
+
| `tcpmapping start` | Start server in background | 后台启动服务 |
|
|
48
|
+
| `tcpmapping stop` | Stop server | 停止服务 |
|
|
49
|
+
| `tcpmapping restart` | Restart server | 重启服务 |
|
|
50
|
+
| `tcpmapping status` | Show status and config paths | 查看状态和配置路径 |
|
|
51
|
+
| `tcpmapping logs` | Show recent logs | 查看最近日志 |
|
|
52
|
+
| `tcpmapping logs -f` | Follow logs in real-time | 实时跟踪日志 |
|
|
53
|
+
| `tcpmapping setup-password` | Set admin password | 设置管理员密码 |
|
|
54
|
+
| `tcpmapping init` | Create config file | 创建配置文件 |
|
|
55
|
+
|
|
56
|
+
---
|
|
49
57
|
|
|
50
|
-
|
|
58
|
+
## WebUI
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|------|------|
|
|
54
|
-
| `tcpmapping start` | 后台启动服务 |
|
|
55
|
-
| `tcpmapping stop` | 停止服务 |
|
|
56
|
-
| `tcpmapping restart` | 重启服务 |
|
|
57
|
-
| `tcpmapping status` | 查看服务状态 |
|
|
58
|
-
| `tcpmapping logs` | 查看最近日志 |
|
|
59
|
-
| `tcpmapping logs -f` | 实时跟踪日志 |
|
|
60
|
-
| `tcpmapping setup-password` | 设置管理员密码 |
|
|
61
|
-
| `tcpmapping init` | 创建默认配置文件 |
|
|
62
|
-
| `tcpmapping version` | 显示版本号 |
|
|
63
|
-
| `tcpmapping help` | 显示帮助信息 |
|
|
60
|
+
After starting, visit **http://localhost:9999**
|
|
64
61
|
|
|
65
|
-
|
|
62
|
+
启动后访问 **http://localhost:9999**
|
|
66
63
|
|
|
67
|
-
|
|
64
|
+
Default username | 默认用户名: `admin`
|
|
68
65
|
|
|
69
|
-
|
|
66
|
+
---
|
|
70
67
|
|
|
71
|
-
##
|
|
68
|
+
## Configuration | 配置
|
|
72
69
|
|
|
73
|
-
|
|
70
|
+
Config file location (priority order) | 配置文件位置(优先级):
|
|
71
|
+
1. `./config.json` - Current directory | 当前目录
|
|
72
|
+
2. `~/.tcpmapping/config.json` - User data directory | 用户数据目录
|
|
73
|
+
|
|
74
|
+
### Example | 示例
|
|
74
75
|
|
|
75
76
|
```json
|
|
76
77
|
{
|
|
77
78
|
"mappings": [
|
|
78
79
|
{
|
|
79
|
-
"name": "
|
|
80
|
+
"name": "web-server",
|
|
80
81
|
"listen": { "host": "0.0.0.0", "port": 8080 },
|
|
81
82
|
"target": { "host": "192.168.1.100", "port": 3000 }
|
|
82
83
|
},
|
|
83
84
|
{
|
|
84
|
-
"name": "
|
|
85
|
-
"listen": { "host": "
|
|
86
|
-
"target": { "host": "
|
|
85
|
+
"name": "database",
|
|
86
|
+
"listen": { "host": "127.0.0.1", "port": 3307 },
|
|
87
|
+
"target": { "host": "db.example.com", "port": 3306 }
|
|
87
88
|
}
|
|
88
89
|
],
|
|
89
|
-
"webui": {
|
|
90
|
-
|
|
91
|
-
"port": 9999,
|
|
92
|
-
"host": "0.0.0.0"
|
|
93
|
-
},
|
|
94
|
-
"logging": {
|
|
95
|
-
"enabled": true,
|
|
96
|
-
"level": "info"
|
|
97
|
-
}
|
|
90
|
+
"webui": { "port": 9999 },
|
|
91
|
+
"logging": { "level": "info" }
|
|
98
92
|
}
|
|
99
93
|
```
|
|
100
94
|
|
|
101
|
-
###
|
|
95
|
+
### Config Options | 配置项
|
|
102
96
|
|
|
103
|
-
|
|
|
104
|
-
|
|
105
|
-
| `mappings[].name` |
|
|
106
|
-
| `mappings[].listen.host` |
|
|
107
|
-
| `mappings[].listen.port` | 监听端口 |
|
|
108
|
-
| `mappings[].target.host` |
|
|
109
|
-
| `mappings[].target.port` | 目标端口 |
|
|
110
|
-
| `webui.
|
|
111
|
-
| `
|
|
112
|
-
| `logging.level` | 日志级别:debug/info/warn/error |
|
|
97
|
+
| Option | Description |
|
|
98
|
+
|--------|-------------|
|
|
99
|
+
| `mappings[].name` | Mapping name | 映射名称 |
|
|
100
|
+
| `mappings[].listen.host` | Listen address (0.0.0.0 for all) | 监听地址 |
|
|
101
|
+
| `mappings[].listen.port` | Listen port | 监听端口 |
|
|
102
|
+
| `mappings[].target.host` | Target host IP or domain | 目标主机 |
|
|
103
|
+
| `mappings[].target.port` | Target port | 目标端口 |
|
|
104
|
+
| `webui.port` | WebUI port (default: 9999) | WebUI端口 |
|
|
105
|
+
| `logging.level` | Log level: debug/info/warn/error | 日志级别 |
|
|
113
106
|
|
|
114
|
-
|
|
107
|
+
---
|
|
115
108
|
|
|
116
|
-
|
|
109
|
+
## Data Persistence | 数据持久化
|
|
117
110
|
|
|
118
|
-
|
|
111
|
+
User configuration is stored in `~/.tcpmapping/`:
|
|
119
112
|
|
|
120
|
-
|
|
121
|
-
{
|
|
122
|
-
"name": "开发服务",
|
|
123
|
-
"listen": { "host": "0.0.0.0", "port": 8080 },
|
|
124
|
-
"target": { "host": "127.0.0.1", "port": 3000 }
|
|
125
|
-
}
|
|
126
|
-
```
|
|
113
|
+
用户配置存储在 `~/.tcpmapping/` 目录:
|
|
127
114
|
|
|
128
|
-
|
|
115
|
+
- `config.json` - Port mapping configuration | 端口映射配置
|
|
116
|
+
- `auth-config.json` - Authentication configuration | 认证配置
|
|
117
|
+
- `tcpmapping.log` - Log file | 日志文件
|
|
118
|
+
- `tcpmapping.pid` - PID file | PID文件
|
|
129
119
|
|
|
130
|
-
|
|
120
|
+
**Upgrading will NOT overwrite your configuration.**
|
|
131
121
|
|
|
132
|
-
|
|
133
|
-
{
|
|
134
|
-
"name": "MySQL",
|
|
135
|
-
"listen": { "host": "127.0.0.1", "port": 3307 },
|
|
136
|
-
"target": { "host": "db.example.com", "port": 3306 }
|
|
137
|
-
}
|
|
138
|
-
```
|
|
122
|
+
**升级不会覆盖你的配置文件。**
|
|
139
123
|
|
|
140
|
-
|
|
124
|
+
---
|
|
141
125
|
|
|
142
|
-
|
|
143
|
-
2. 登录管理界面
|
|
144
|
-
3. 点击"添加映射"按钮
|
|
145
|
-
4. 填写映射信息并保存
|
|
126
|
+
## Security | 安全特性
|
|
146
127
|
|
|
147
|
-
|
|
128
|
+
- **Strong Password Policy** - 8+ chars, uppercase, lowercase, numbers | 强密码策略
|
|
129
|
+
- **Login Protection** - Lock after 5 failed attempts for 30 minutes | 登录保护
|
|
130
|
+
- **Session Timeout** - 24 hours auto expiration | 会话超时
|
|
131
|
+
- **Password Encryption** - bcrypt with 12 rounds | bcrypt加密
|
|
148
132
|
|
|
149
|
-
|
|
150
|
-
- **登录保护** - 5次失败后锁定30分钟
|
|
151
|
-
- **会话超时** - 24小时自动过期
|
|
152
|
-
- **密码加密** - bcrypt 12轮哈希
|
|
133
|
+
---
|
|
153
134
|
|
|
154
|
-
## API
|
|
135
|
+
## API
|
|
136
|
+
|
|
137
|
+
See [docs/API.md](docs/API.md) for API documentation.
|
|
155
138
|
|
|
156
139
|
详细API文档请参阅 [docs/API.md](docs/API.md)
|
|
157
140
|
|
|
158
|
-
### 快速参考
|
|
141
|
+
### Quick Reference | 快速参考
|
|
159
142
|
|
|
160
143
|
```bash
|
|
161
|
-
#
|
|
144
|
+
# Health check (no auth) | 健康检查
|
|
162
145
|
curl http://localhost:9999/api/health
|
|
163
146
|
|
|
164
|
-
# 登录
|
|
147
|
+
# Login | 登录
|
|
165
148
|
curl -X POST http://localhost:9999/api/login \
|
|
166
149
|
-H "Content-Type: application/json" \
|
|
167
150
|
-d '{"username":"admin","password":"YourPassword"}' \
|
|
168
151
|
-c cookies.txt
|
|
169
152
|
|
|
170
|
-
# 获取映射列表
|
|
153
|
+
# Get mappings | 获取映射列表
|
|
171
154
|
curl http://localhost:9999/api/mappings -b cookies.txt
|
|
172
|
-
|
|
173
|
-
# 添加映射
|
|
174
|
-
curl -X POST http://localhost:9999/api/mappings \
|
|
175
|
-
-H "Content-Type: application/json" \
|
|
176
|
-
-d '{"name":"测试","listen":{"port":8888},"target":{"host":"127.0.0.1","port":80}}' \
|
|
177
|
-
-b cookies.txt
|
|
178
155
|
```
|
|
179
156
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
```
|
|
183
|
-
├── bin/
|
|
184
|
-
│ └── tcpmapping.js # CLI入口
|
|
185
|
-
├── public/
|
|
186
|
-
│ ├── login.html # 登录页面
|
|
187
|
-
│ └── dashboard.html # 管理面板
|
|
188
|
-
├── docs/
|
|
189
|
-
│ └── API.md # API文档
|
|
190
|
-
├── server.js # 主服务器
|
|
191
|
-
├── setup-password.js # 密码设置工具
|
|
192
|
-
├── config.json # 端口映射配置
|
|
193
|
-
└── package.json # 项目配置
|
|
194
|
-
```
|
|
157
|
+
---
|
|
195
158
|
|
|
196
|
-
## 常见问题
|
|
159
|
+
## FAQ | 常见问题
|
|
197
160
|
|
|
198
|
-
###
|
|
161
|
+
### Forgot password? | 忘记密码?
|
|
199
162
|
|
|
200
|
-
运行密码重置命令:
|
|
201
163
|
```bash
|
|
202
|
-
# NPM全局安装
|
|
203
164
|
tcpmapping setup-password
|
|
204
|
-
|
|
205
|
-
# 本地安装
|
|
165
|
+
# or | 或
|
|
206
166
|
npm run reset-password
|
|
207
167
|
```
|
|
208
168
|
|
|
209
|
-
###
|
|
169
|
+
### Check if running? | 检查运行状态?
|
|
210
170
|
|
|
211
171
|
```bash
|
|
212
172
|
tcpmapping status
|
|
213
173
|
```
|
|
214
174
|
|
|
215
|
-
###
|
|
175
|
+
### Port already in use? | 端口被占用?
|
|
216
176
|
|
|
217
|
-
|
|
177
|
+
Edit `webui.port` or `mappings[].listen.port` in config.json.
|
|
218
178
|
|
|
219
|
-
|
|
179
|
+
修改 config.json 中的 `webui.port` 或 `mappings[].listen.port`。
|
|
220
180
|
|
|
221
|
-
|
|
222
|
-
tcpmapping logs -f
|
|
223
|
-
```
|
|
181
|
+
---
|
|
224
182
|
|
|
225
|
-
## 许可证
|
|
183
|
+
## License | 许可证
|
|
226
184
|
|
|
227
185
|
MIT License
|
package/bin/tcpmapping.js
CHANGED
|
@@ -6,8 +6,9 @@ const fs = require('fs');
|
|
|
6
6
|
const os = require('os');
|
|
7
7
|
|
|
8
8
|
const APP_NAME = 'tcpmapping';
|
|
9
|
-
const
|
|
10
|
-
const
|
|
9
|
+
const DATA_DIR = path.join(os.homedir(), '.tcpmapping');
|
|
10
|
+
const PID_FILE = path.join(DATA_DIR, 'tcpmapping.pid');
|
|
11
|
+
const LOG_FILE = path.join(DATA_DIR, 'tcpmapping.log');
|
|
11
12
|
const SERVER_FILE = path.join(__dirname, '..', 'server.js');
|
|
12
13
|
const SETUP_PASSWORD_FILE = path.join(__dirname, '..', 'setup-password.js');
|
|
13
14
|
|
|
@@ -21,11 +22,52 @@ const colors = {
|
|
|
21
22
|
red: (text) => `\x1b[31m${text}\x1b[0m`,
|
|
22
23
|
yellow: (text) => `\x1b[33m${text}\x1b[0m`,
|
|
23
24
|
blue: (text) => `\x1b[34m${text}\x1b[0m`,
|
|
24
|
-
cyan: (text) => `\x1b[36m${text}\x1b[0m
|
|
25
|
+
cyan: (text) => `\x1b[36m${text}\x1b[0m`,
|
|
26
|
+
gray: (text) => `\x1b[90m${text}\x1b[0m`
|
|
25
27
|
};
|
|
26
28
|
|
|
29
|
+
// 确保数据目录存在
|
|
30
|
+
function ensureDataDir() {
|
|
31
|
+
if (!fs.existsSync(DATA_DIR)) {
|
|
32
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 获取配置文件路径(按优先级)
|
|
37
|
+
function getConfigPath() {
|
|
38
|
+
// 优先级1: 当前工作目录
|
|
39
|
+
const cwdPath = path.join(process.cwd(), 'config.json');
|
|
40
|
+
if (fs.existsSync(cwdPath)) {
|
|
41
|
+
return cwdPath;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 优先级2: 用户数据目录
|
|
45
|
+
const dataPath = path.join(DATA_DIR, 'config.json');
|
|
46
|
+
if (fs.existsSync(dataPath)) {
|
|
47
|
+
return dataPath;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 获取认证配置文件路径
|
|
54
|
+
function getAuthConfigPath() {
|
|
55
|
+
const cwdPath = path.join(process.cwd(), 'auth-config.json');
|
|
56
|
+
if (fs.existsSync(cwdPath)) {
|
|
57
|
+
return cwdPath;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const dataPath = path.join(DATA_DIR, 'auth-config.json');
|
|
61
|
+
if (fs.existsSync(dataPath)) {
|
|
62
|
+
return dataPath;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
27
68
|
// 检查进程是否运行
|
|
28
69
|
function isRunning() {
|
|
70
|
+
ensureDataDir();
|
|
29
71
|
if (!fs.existsSync(PID_FILE)) {
|
|
30
72
|
return false;
|
|
31
73
|
}
|
|
@@ -50,6 +92,17 @@ function start() {
|
|
|
50
92
|
return;
|
|
51
93
|
}
|
|
52
94
|
|
|
95
|
+
// 检查是否已初始化
|
|
96
|
+
const configPath = getConfigPath();
|
|
97
|
+
const authPath = getAuthConfigPath();
|
|
98
|
+
|
|
99
|
+
if (!authPath) {
|
|
100
|
+
console.log(colors.yellow('未设置管理员密码,请先运行:'));
|
|
101
|
+
console.log(colors.cyan(' tcpmapping setup-password'));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
ensureDataDir();
|
|
53
106
|
console.log(colors.blue(`Starting ${APP_NAME}...`));
|
|
54
107
|
|
|
55
108
|
const out = fs.openSync(LOG_FILE, 'a');
|
|
@@ -58,7 +111,7 @@ function start() {
|
|
|
58
111
|
const child = spawn('node', [SERVER_FILE], {
|
|
59
112
|
detached: true,
|
|
60
113
|
stdio: ['ignore', out, err],
|
|
61
|
-
cwd:
|
|
114
|
+
cwd: process.cwd() // 使用当前目录,以便server可以找到当前目录的config
|
|
62
115
|
});
|
|
63
116
|
|
|
64
117
|
fs.writeFileSync(PID_FILE, child.pid.toString());
|
|
@@ -66,7 +119,10 @@ function start() {
|
|
|
66
119
|
|
|
67
120
|
console.log(colors.green(`${APP_NAME} started successfully (PID: ${child.pid})`));
|
|
68
121
|
console.log(colors.cyan(`WebUI: http://localhost:9999`));
|
|
69
|
-
console.log(colors.
|
|
122
|
+
console.log(colors.gray(`Log file: ${LOG_FILE}`));
|
|
123
|
+
if (configPath) {
|
|
124
|
+
console.log(colors.gray(`Config: ${configPath}`));
|
|
125
|
+
}
|
|
70
126
|
}
|
|
71
127
|
|
|
72
128
|
// 停止服务
|
|
@@ -126,8 +182,14 @@ function restart() {
|
|
|
126
182
|
// 查看状态
|
|
127
183
|
function status() {
|
|
128
184
|
const pid = isRunning();
|
|
185
|
+
const configPath = getConfigPath();
|
|
186
|
+
const authPath = getAuthConfigPath();
|
|
187
|
+
|
|
188
|
+
console.log(colors.cyan('=== TcpMapping Status ===\n'));
|
|
189
|
+
|
|
190
|
+
// 服务状态
|
|
129
191
|
if (pid) {
|
|
130
|
-
console.log(colors.green(
|
|
192
|
+
console.log(colors.green(`Service: Running (PID: ${pid})`));
|
|
131
193
|
|
|
132
194
|
// 尝试获取健康状态
|
|
133
195
|
try {
|
|
@@ -138,23 +200,36 @@ function status() {
|
|
|
138
200
|
res.on('end', () => {
|
|
139
201
|
try {
|
|
140
202
|
const health = JSON.parse(data);
|
|
141
|
-
console.log(
|
|
142
|
-
console.log(
|
|
143
|
-
console.log(
|
|
144
|
-
console.log(colors.cyan(` Active Connections: ${health.activeConnections}`));
|
|
203
|
+
console.log(` Uptime: ${Math.floor(health.uptime / 60)} minutes`);
|
|
204
|
+
console.log(` Active Mappings: ${health.activeMappings}`);
|
|
205
|
+
console.log(` Active Connections: ${health.activeConnections}`);
|
|
145
206
|
} catch (e) {}
|
|
207
|
+
showPaths();
|
|
146
208
|
});
|
|
147
209
|
});
|
|
148
|
-
req.on('error', () =>
|
|
149
|
-
req.on('timeout', () => req.destroy());
|
|
150
|
-
} catch (e) {
|
|
210
|
+
req.on('error', () => showPaths());
|
|
211
|
+
req.on('timeout', () => { req.destroy(); showPaths(); });
|
|
212
|
+
} catch (e) {
|
|
213
|
+
showPaths();
|
|
214
|
+
}
|
|
151
215
|
} else {
|
|
152
|
-
console.log(colors.red(
|
|
216
|
+
console.log(colors.red('Service: Stopped'));
|
|
217
|
+
showPaths();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function showPaths() {
|
|
221
|
+
console.log('');
|
|
222
|
+
console.log(colors.cyan('=== Configuration ===\n'));
|
|
223
|
+
console.log(`Data Dir: ${DATA_DIR}`);
|
|
224
|
+
console.log(`Config: ${configPath || colors.yellow('Not found (run: tcpmapping init)')}`);
|
|
225
|
+
console.log(`Auth: ${authPath || colors.yellow('Not found (run: tcpmapping setup-password)')}`);
|
|
226
|
+
console.log(`Log: ${LOG_FILE}`);
|
|
153
227
|
}
|
|
154
228
|
}
|
|
155
229
|
|
|
156
230
|
// 查看日志
|
|
157
231
|
function logs(follow = false) {
|
|
232
|
+
ensureDataDir();
|
|
158
233
|
if (!fs.existsSync(LOG_FILE)) {
|
|
159
234
|
console.log(colors.yellow('No log file found'));
|
|
160
235
|
return;
|
|
@@ -176,10 +251,15 @@ function logs(follow = false) {
|
|
|
176
251
|
|
|
177
252
|
// 设置密码
|
|
178
253
|
function setupPassword() {
|
|
254
|
+
ensureDataDir();
|
|
179
255
|
console.log(colors.blue('Setting up password...'));
|
|
256
|
+
console.log(colors.gray(`Config will be saved to: ${DATA_DIR}`));
|
|
257
|
+
console.log('');
|
|
258
|
+
|
|
180
259
|
const child = spawn('node', [SETUP_PASSWORD_FILE], {
|
|
181
260
|
stdio: 'inherit',
|
|
182
|
-
cwd:
|
|
261
|
+
cwd: DATA_DIR, // 在数据目录运行,使auth-config.json保存到数据目录
|
|
262
|
+
env: { ...process.env, TCPMAPPING_DATA_DIR: DATA_DIR }
|
|
183
263
|
});
|
|
184
264
|
|
|
185
265
|
child.on('exit', (code) => {
|
|
@@ -189,29 +269,41 @@ function setupPassword() {
|
|
|
189
269
|
|
|
190
270
|
// 初始化配置
|
|
191
271
|
function init() {
|
|
192
|
-
|
|
193
|
-
|
|
272
|
+
ensureDataDir();
|
|
273
|
+
|
|
274
|
+
// 检查当前目录和数据目录是否已有配置
|
|
275
|
+
const cwdConfig = path.join(process.cwd(), 'config.json');
|
|
276
|
+
const dataConfig = path.join(DATA_DIR, 'config.json');
|
|
277
|
+
|
|
278
|
+
if (fs.existsSync(cwdConfig)) {
|
|
279
|
+
console.log(colors.yellow(`config.json already exists in current directory`));
|
|
280
|
+
console.log(colors.gray(` ${cwdConfig}`));
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
194
283
|
|
|
195
|
-
if (fs.existsSync(
|
|
196
|
-
console.log(colors.yellow(
|
|
284
|
+
if (fs.existsSync(dataConfig)) {
|
|
285
|
+
console.log(colors.yellow(`config.json already exists in data directory`));
|
|
286
|
+
console.log(colors.gray(` ${dataConfig}`));
|
|
287
|
+
console.log('');
|
|
288
|
+
console.log('To create a local config, copy it to current directory:');
|
|
289
|
+
console.log(colors.cyan(` cp ${dataConfig} ./config.json`));
|
|
197
290
|
return;
|
|
198
291
|
}
|
|
199
292
|
|
|
200
293
|
const defaultConfig = {
|
|
201
|
-
mappings: [
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
listen: { host: "0.0.0.0", port: 8080 },
|
|
205
|
-
target: { host: "127.0.0.1", port: 3000 }
|
|
206
|
-
}
|
|
207
|
-
],
|
|
208
|
-
webui: { enabled: true, port: 9999, host: "0.0.0.0" },
|
|
209
|
-
logging: { enabled: true, level: "info" }
|
|
294
|
+
mappings: [],
|
|
295
|
+
webui: { port: 9999 },
|
|
296
|
+
logging: { level: "info" }
|
|
210
297
|
};
|
|
211
298
|
|
|
212
|
-
|
|
299
|
+
// 保存到数据目录
|
|
300
|
+
fs.writeFileSync(dataConfig, JSON.stringify(defaultConfig, null, 2));
|
|
213
301
|
console.log(colors.green('Created config.json'));
|
|
214
|
-
console.log(colors.
|
|
302
|
+
console.log(colors.gray(` ${dataConfig}`));
|
|
303
|
+
console.log('');
|
|
304
|
+
console.log('Edit the config file to add your port mappings.');
|
|
305
|
+
console.log('Or copy it to current directory for project-specific config:');
|
|
306
|
+
console.log(colors.cyan(` cp ${dataConfig} ./config.json`));
|
|
215
307
|
}
|
|
216
308
|
|
|
217
309
|
// 显示版本
|
|
@@ -232,7 +324,7 @@ ${colors.yellow('Commands:')}
|
|
|
232
324
|
${colors.green('start')} Start the server in background
|
|
233
325
|
${colors.green('stop')} Stop the server
|
|
234
326
|
${colors.green('restart')} Restart the server
|
|
235
|
-
${colors.green('status')} Show server status
|
|
327
|
+
${colors.green('status')} Show server status and config paths
|
|
236
328
|
${colors.green('logs')} Show recent logs
|
|
237
329
|
${colors.green('logs -f')} Follow logs in real-time
|
|
238
330
|
${colors.green('setup-password')} Set up admin password
|
|
@@ -240,10 +332,15 @@ ${colors.yellow('Commands:')}
|
|
|
240
332
|
${colors.green('version')} Show version
|
|
241
333
|
${colors.green('help')} Show this help message
|
|
242
334
|
|
|
335
|
+
${colors.yellow('Config Paths (priority order):')}
|
|
336
|
+
1. ./config.json (current directory - project config)
|
|
337
|
+
2. ~/.tcpmapping/ (user data directory - global config)
|
|
338
|
+
|
|
243
339
|
${colors.yellow('Examples:')}
|
|
244
|
-
tcpmapping
|
|
245
|
-
tcpmapping
|
|
246
|
-
tcpmapping
|
|
340
|
+
tcpmapping init # Create config file
|
|
341
|
+
tcpmapping setup-password # Set admin password
|
|
342
|
+
tcpmapping start # Start server
|
|
343
|
+
tcpmapping status # Check status and config paths
|
|
247
344
|
|
|
248
345
|
${colors.yellow('WebUI:')}
|
|
249
346
|
After starting, visit http://localhost:9999
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chenpu17/tcpmapping",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "TCP端口转发服务器,支持多端口映射、WebUI管理界面和实时连接监控",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
"bin/",
|
|
17
17
|
"public/",
|
|
18
18
|
"server.js",
|
|
19
|
-
"setup-password.js"
|
|
20
|
-
"config.json"
|
|
19
|
+
"setup-password.js"
|
|
21
20
|
],
|
|
22
21
|
"keywords": [
|
|
23
22
|
"tcp",
|
package/server.js
CHANGED
|
@@ -1,11 +1,59 @@
|
|
|
1
1
|
const net = require('net');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
4
5
|
const express = require('express');
|
|
5
6
|
const session = require('express-session');
|
|
6
7
|
const bcrypt = require('bcryptjs');
|
|
7
8
|
const bodyParser = require('body-parser');
|
|
8
9
|
|
|
10
|
+
// 数据目录 - 用于存储用户配置(持久化)
|
|
11
|
+
const DATA_DIR = path.join(os.homedir(), '.tcpmapping');
|
|
12
|
+
|
|
13
|
+
// 获取配置文件路径(按优先级搜索)
|
|
14
|
+
function getConfigPath(filename) {
|
|
15
|
+
// 优先级1: 当前工作目录
|
|
16
|
+
const cwdPath = path.join(process.cwd(), filename);
|
|
17
|
+
if (fs.existsSync(cwdPath)) {
|
|
18
|
+
return cwdPath;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 优先级2: 用户数据目录 (~/.tcpmapping/)
|
|
22
|
+
const dataPath = path.join(DATA_DIR, filename);
|
|
23
|
+
if (fs.existsSync(dataPath)) {
|
|
24
|
+
return dataPath;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 优先级3: 包安装目录(仅作为模板)
|
|
28
|
+
const pkgPath = path.join(__dirname, filename);
|
|
29
|
+
if (fs.existsSync(pkgPath)) {
|
|
30
|
+
return pkgPath;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 获取配置保存路径
|
|
37
|
+
function getConfigSavePath(filename) {
|
|
38
|
+
// 如果当前工作目录有配置文件,保存到当前目录
|
|
39
|
+
const cwdPath = path.join(process.cwd(), filename);
|
|
40
|
+
if (fs.existsSync(cwdPath)) {
|
|
41
|
+
return cwdPath;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 否则保存到用户数据目录
|
|
45
|
+
ensureDataDir();
|
|
46
|
+
return path.join(DATA_DIR, filename);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 确保数据目录存在
|
|
50
|
+
function ensureDataDir() {
|
|
51
|
+
if (!fs.existsSync(DATA_DIR)) {
|
|
52
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
53
|
+
console.log(`创建配置目录: ${DATA_DIR}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
9
57
|
// 全局变量
|
|
10
58
|
let config = {};
|
|
11
59
|
const servers = new Map(); // 存储所有TCP转发服务器实例
|
|
@@ -21,12 +69,16 @@ const loginAttempts = new Map();
|
|
|
21
69
|
// 加载认证配置
|
|
22
70
|
function loadAuthConfig() {
|
|
23
71
|
try {
|
|
24
|
-
const authConfigPath =
|
|
72
|
+
const authConfigPath = getConfigPath('auth-config.json');
|
|
73
|
+
if (!authConfigPath) {
|
|
74
|
+
throw new Error('认证配置文件不存在');
|
|
75
|
+
}
|
|
25
76
|
const authConfigData = fs.readFileSync(authConfigPath, 'utf8');
|
|
77
|
+
console.log(`加载认证配置: ${authConfigPath}`);
|
|
26
78
|
return JSON.parse(authConfigData);
|
|
27
79
|
} catch (error) {
|
|
28
80
|
console.error('警告: 未找到认证配置文件,使用默认配置');
|
|
29
|
-
console.error('请运行 "
|
|
81
|
+
console.error('请运行 "tcpmapping setup-password" 或 "npm run setup-password" 来设置安全密码');
|
|
30
82
|
|
|
31
83
|
// 返回默认配置(仅用于向后兼容)
|
|
32
84
|
return {
|
|
@@ -125,15 +177,21 @@ const LOG_LEVELS = {
|
|
|
125
177
|
// 读取配置文件
|
|
126
178
|
function loadConfig() {
|
|
127
179
|
try {
|
|
128
|
-
const configPath =
|
|
180
|
+
const configPath = getConfigPath('config.json');
|
|
181
|
+
if (!configPath) {
|
|
182
|
+
console.log('未找到配置文件,使用默认配置');
|
|
183
|
+
console.log('运行 "tcpmapping init" 创建配置文件');
|
|
184
|
+
return getDefaultConfig();
|
|
185
|
+
}
|
|
129
186
|
const configData = fs.readFileSync(configPath, 'utf8');
|
|
130
187
|
const loadedConfig = JSON.parse(configData);
|
|
131
|
-
|
|
188
|
+
console.log(`加载配置文件: ${configPath}`);
|
|
189
|
+
|
|
132
190
|
// 验证配置
|
|
133
191
|
if (!loadedConfig.mappings || !Array.isArray(loadedConfig.mappings)) {
|
|
134
192
|
loadedConfig.mappings = [];
|
|
135
193
|
}
|
|
136
|
-
|
|
194
|
+
|
|
137
195
|
// 添加默认WebUI配置
|
|
138
196
|
if (!loadedConfig.webui) {
|
|
139
197
|
loadedConfig.webui = {
|
|
@@ -142,32 +200,37 @@ function loadConfig() {
|
|
|
142
200
|
host: '0.0.0.0'
|
|
143
201
|
};
|
|
144
202
|
}
|
|
145
|
-
|
|
203
|
+
|
|
146
204
|
return loadedConfig;
|
|
147
205
|
} catch (error) {
|
|
148
206
|
console.error('读取配置文件失败:', error.message);
|
|
149
|
-
|
|
150
|
-
return {
|
|
151
|
-
mappings: [],
|
|
152
|
-
webui: {
|
|
153
|
-
enabled: true,
|
|
154
|
-
port: 9999,
|
|
155
|
-
host: '0.0.0.0'
|
|
156
|
-
},
|
|
157
|
-
logging: {
|
|
158
|
-
enabled: true,
|
|
159
|
-
level: 'info'
|
|
160
|
-
}
|
|
161
|
-
};
|
|
207
|
+
return getDefaultConfig();
|
|
162
208
|
}
|
|
163
209
|
}
|
|
164
210
|
|
|
211
|
+
// 获取默认配置
|
|
212
|
+
function getDefaultConfig() {
|
|
213
|
+
return {
|
|
214
|
+
mappings: [],
|
|
215
|
+
webui: {
|
|
216
|
+
enabled: true,
|
|
217
|
+
port: 9999,
|
|
218
|
+
host: '0.0.0.0'
|
|
219
|
+
},
|
|
220
|
+
logging: {
|
|
221
|
+
enabled: true,
|
|
222
|
+
level: 'info'
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
165
227
|
// 保存配置文件
|
|
166
228
|
function saveConfig(newConfig) {
|
|
167
229
|
try {
|
|
168
|
-
const configPath =
|
|
230
|
+
const configPath = getConfigSavePath('config.json');
|
|
169
231
|
fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2));
|
|
170
232
|
config = newConfig;
|
|
233
|
+
console.log(`配置已保存: ${configPath}`);
|
|
171
234
|
return true;
|
|
172
235
|
} catch (error) {
|
|
173
236
|
console.error('保存配置文件失败:', error.message);
|
package/setup-password.js
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
3
4
|
const bcrypt = require('bcryptjs');
|
|
4
5
|
const readline = require('readline');
|
|
5
6
|
|
|
7
|
+
// 获取配置保存目录
|
|
8
|
+
function getConfigDir() {
|
|
9
|
+
// 优先使用环境变量指定的目录
|
|
10
|
+
if (process.env.TCPMAPPING_DATA_DIR) {
|
|
11
|
+
return process.env.TCPMAPPING_DATA_DIR;
|
|
12
|
+
}
|
|
13
|
+
// 其次使用当前工作目录
|
|
14
|
+
return process.cwd();
|
|
15
|
+
}
|
|
16
|
+
|
|
6
17
|
// 创建readline接口
|
|
7
18
|
const rl = readline.createInterface({
|
|
8
19
|
input: process.stdin,
|
|
@@ -44,36 +55,70 @@ function validatePassword(password) {
|
|
|
44
55
|
// 获取用户输入(隐藏密码)
|
|
45
56
|
function getPassword(prompt) {
|
|
46
57
|
return new Promise((resolve) => {
|
|
47
|
-
|
|
58
|
+
const rl2 = readline.createInterface({
|
|
59
|
+
input: process.stdin,
|
|
60
|
+
output: process.stdout
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Windows 兼容:使用简单的输入方式
|
|
64
|
+
// 在某些终端上密码可能可见,但这是最兼容的方式
|
|
65
|
+
if (process.platform === 'win32') {
|
|
66
|
+
// Windows: 尝试隐藏输入
|
|
67
|
+
const spawn = require('child_process').spawnSync;
|
|
68
|
+
process.stdout.write(prompt);
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// 使用 PowerShell 读取密码
|
|
72
|
+
const result = spawn('powershell', [
|
|
73
|
+
'-Command',
|
|
74
|
+
'$pwd = Read-Host -AsSecureString; [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pwd))'
|
|
75
|
+
], { stdio: ['inherit', 'pipe', 'inherit'] });
|
|
76
|
+
|
|
77
|
+
if (result.status === 0 && result.stdout) {
|
|
78
|
+
resolve(result.stdout.toString().trim());
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
} catch (e) {
|
|
82
|
+
// PowerShell 不可用,回退到普通输入
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 回退方案:普通输入(密码可见)
|
|
86
|
+
console.log('(密码将可见)');
|
|
87
|
+
rl2.question('', (answer) => {
|
|
88
|
+
rl2.close();
|
|
89
|
+
resolve(answer);
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Unix/Mac: 使用 raw mode 隐藏密码
|
|
48
95
|
process.stdout.write(prompt);
|
|
49
96
|
process.stdin.setRawMode(true);
|
|
50
97
|
process.stdin.setEncoding('utf8');
|
|
51
|
-
|
|
98
|
+
|
|
52
99
|
let password = '';
|
|
53
|
-
|
|
100
|
+
|
|
54
101
|
const onData = (char) => {
|
|
55
102
|
if (char === '\n' || char === '\r' || char === '\u0004') {
|
|
56
|
-
// 结束输入
|
|
57
103
|
process.stdin.setRawMode(false);
|
|
58
104
|
process.stdin.removeListener('data', onData);
|
|
105
|
+
process.stdin.pause();
|
|
59
106
|
process.stdout.write('\n');
|
|
60
107
|
resolve(password);
|
|
61
108
|
} else if (char === '\u0003') {
|
|
62
|
-
// Ctrl+C
|
|
63
109
|
process.exit();
|
|
64
|
-
} else if (char === '\u007f') {
|
|
65
|
-
// 退格键
|
|
110
|
+
} else if (char === '\u007f' || char === '\b') {
|
|
66
111
|
if (password.length > 0) {
|
|
67
112
|
password = password.slice(0, -1);
|
|
68
113
|
process.stdout.write('\b \b');
|
|
69
114
|
}
|
|
70
|
-
} else {
|
|
71
|
-
// 普通字符
|
|
115
|
+
} else if (char.charCodeAt(0) >= 32) {
|
|
72
116
|
password += char;
|
|
73
117
|
process.stdout.write('*');
|
|
74
118
|
}
|
|
75
119
|
};
|
|
76
|
-
|
|
120
|
+
|
|
121
|
+
process.stdin.resume();
|
|
77
122
|
process.stdin.on('data', onData);
|
|
78
123
|
});
|
|
79
124
|
}
|
|
@@ -81,7 +126,7 @@ function getPassword(prompt) {
|
|
|
81
126
|
// 读取现有的认证配置
|
|
82
127
|
function loadAuthConfig() {
|
|
83
128
|
try {
|
|
84
|
-
const configPath = path.join(
|
|
129
|
+
const configPath = path.join(getConfigDir(), 'auth-config.json');
|
|
85
130
|
const configData = fs.readFileSync(configPath, 'utf8');
|
|
86
131
|
return JSON.parse(configData);
|
|
87
132
|
} catch (error) {
|
|
@@ -107,8 +152,14 @@ function loadAuthConfig() {
|
|
|
107
152
|
|
|
108
153
|
// 保存认证配置
|
|
109
154
|
function saveAuthConfig(config) {
|
|
110
|
-
const
|
|
155
|
+
const configDir = getConfigDir();
|
|
156
|
+
// 确保目录存在
|
|
157
|
+
if (!fs.existsSync(configDir)) {
|
|
158
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
159
|
+
}
|
|
160
|
+
const configPath = path.join(configDir, 'auth-config.json');
|
|
111
161
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
162
|
+
console.log(`配置已保存到: ${configPath}`);
|
|
112
163
|
}
|
|
113
164
|
|
|
114
165
|
// 主要设置流程
|
package/config.json
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"mappings": [
|
|
3
|
-
{
|
|
4
|
-
"name": "Dify",
|
|
5
|
-
"listen": {
|
|
6
|
-
"host": "0.0.0.0",
|
|
7
|
-
"port": 5588
|
|
8
|
-
},
|
|
9
|
-
"target": {
|
|
10
|
-
"host": "113.45.34.202",
|
|
11
|
-
"port": 5588
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"name": "语音助手",
|
|
16
|
-
"listen": {
|
|
17
|
-
"host": "0.0.0.0",
|
|
18
|
-
"port": 3443
|
|
19
|
-
},
|
|
20
|
-
"target": {
|
|
21
|
-
"host": "115.120.49.208",
|
|
22
|
-
"port": 443
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"name": "语音助手",
|
|
27
|
-
"listen": {
|
|
28
|
-
"host": "0.0.0.0",
|
|
29
|
-
"port": 443
|
|
30
|
-
},
|
|
31
|
-
"target": {
|
|
32
|
-
"host": "115.120.49.208",
|
|
33
|
-
"port": 443
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
],
|
|
37
|
-
"webui": {
|
|
38
|
-
"enabled": true,
|
|
39
|
-
"port": 9999,
|
|
40
|
-
"host": "0.0.0.0"
|
|
41
|
-
},
|
|
42
|
-
"logging": {
|
|
43
|
-
"enabled": true,
|
|
44
|
-
"level": "info"
|
|
45
|
-
}
|
|
46
|
-
}
|