@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 CHANGED
@@ -1,227 +1,185 @@
1
1
  # TcpMapping
2
2
 
3
- TCP端口转发服务器,支持多端口映射、WebUI管理界面和实时连接监控。
3
+ TCP port forwarding server with WebUI management and real-time connection monitoring.
4
4
 
5
- ## 功能特性
5
+ TCP端口转发服务器,支持WebUI管理界面和实时连接监控。
6
6
 
7
- - **TCP端口转发** - 将TCP流量转发到后端服务器
8
- - **多端口映射** - 同时管理多个转发规则
9
- - **WebUI管理** - 现代化Web界面,实时监控和管理
10
- - **连接监控** - 实时显示每个映射的连接数和流量统计
11
- - **安全认证** - bcrypt密码加密、登录失败保护、会话管理
7
+ ---
12
8
 
13
- ## 安装方式
9
+ ## Features | 功能特性
14
10
 
15
- ### 方式一:NPM全局安装(推荐)
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
- ```bash
18
- # 全局安装
19
- npm install -g @chenpu17/tcpmapping
17
+ ---
20
18
 
21
- # 初始化配置文件
22
- tcpmapping init
19
+ ## Installation | 安装
23
20
 
24
- # 设置管理员密码
25
- tcpmapping setup-password
21
+ ### NPM Global Install (Recommended) | NPM全局安装(推荐)
22
+
23
+ ```bash
24
+ npm install -g @chenpu17/tcpmapping
26
25
 
27
- # 启动服务
28
- tcpmapping start
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
- ## CLI命令
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
- ## 访问WebUI
62
+ 启动后访问 **http://localhost:9999**
66
63
 
67
- 启动服务后,访问 **http://localhost:9999** 进入管理界面。
64
+ Default username | 默认用户名: `admin`
68
65
 
69
- 默认用户名:`admin`
66
+ ---
70
67
 
71
- ## 配置说明
68
+ ## Configuration | 配置
72
69
 
73
- 编辑 `config.json` 配置端口映射:
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": "Web服务",
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": "0.0.0.0", "port": 3307 },
86
- "target": { "host": "192.168.1.100", "port": 3306 }
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
- "enabled": true,
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` | 监听地址,0.0.0.0表示所有网卡 |
107
- | `mappings[].listen.port` | 监听端口 |
108
- | `mappings[].target.host` | 目标主机IP或域名 |
109
- | `mappings[].target.port` | 目标端口 |
110
- | `webui.enabled` | 是否启用WebUI |
111
- | `webui.port` | WebUI端口,默认9999 |
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
- ### 示例1:转发本地开发服务
109
+ ## Data Persistence | 数据持久化
117
110
 
118
- 将本地8080端口转发到开发服务器的3000端口:
111
+ User configuration is stored in `~/.tcpmapping/`:
119
112
 
120
- ```json
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
- ### 示例2:转发远程数据库
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
- 将本地3307端口转发到远程MySQL:
120
+ **Upgrading will NOT overwrite your configuration.**
131
121
 
132
- ```json
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
- ### 示例3:通过WebUI动态添加
124
+ ---
141
125
 
142
- 1. 访问 http://localhost:9999
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
- - **强密码策略** - 8字符以上,包含大小写字母和数字
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
- ### Q: 忘记密码怎么办?
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
- ### Q: 如何查看服务是否运行?
169
+ ### Check if running? | 检查运行状态?
210
170
 
211
171
  ```bash
212
172
  tcpmapping status
213
173
  ```
214
174
 
215
- ### Q: 端口被占用怎么办?
175
+ ### Port already in use? | 端口被占用?
216
176
 
217
- 修改 `config.json` 中的 `webui.port` 或映射的 `listen.port`。
177
+ Edit `webui.port` or `mappings[].listen.port` in config.json.
218
178
 
219
- ### Q: 如何查看实时日志?
179
+ 修改 config.json 中的 `webui.port` 或 `mappings[].listen.port`。
220
180
 
221
- ```bash
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 PID_FILE = path.join(os.tmpdir(), 'tcpmapping.pid');
10
- const LOG_FILE = path.join(os.tmpdir(), 'tcpmapping.log');
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: path.dirname(SERVER_FILE)
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.cyan(`Log file: ${LOG_FILE}`));
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(`${APP_NAME} is running (PID: ${pid})`));
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(colors.cyan(` Status: ${health.status}`));
142
- console.log(colors.cyan(` Uptime: ${Math.floor(health.uptime / 60)} minutes`));
143
- console.log(colors.cyan(` Active Mappings: ${health.activeMappings}`));
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(`${APP_NAME} is not running`));
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: path.dirname(SETUP_PASSWORD_FILE)
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
- const configDir = process.cwd();
193
- const configFile = path.join(configDir, 'config.json');
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(configFile)) {
196
- console.log(colors.yellow('config.json already exists'));
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
- name: "Example",
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
- fs.writeFileSync(configFile, JSON.stringify(defaultConfig, null, 2));
299
+ // 保存到数据目录
300
+ fs.writeFileSync(dataConfig, JSON.stringify(defaultConfig, null, 2));
213
301
  console.log(colors.green('Created config.json'));
214
- console.log(colors.cyan('Edit config.json to configure your port mappings'));
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 start # Start server
245
- tcpmapping status # Check if server is running
246
- tcpmapping logs -f # Follow logs
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.0.1",
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 = path.join(__dirname, 'auth-config.json');
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('请运行 "node setup-password.js" 来设置安全密码');
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 = path.join(__dirname, 'config.json');
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 = path.join(__dirname, 'config.json');
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(__dirname, 'auth-config.json');
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 configPath = path.join(__dirname, 'auth-config.json');
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
- }