tanyu_admin 1.0.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/.dockerignore +16 -0
- package/.github/workflows/docker-build.yml +52 -0
- package/CLAUDE.md +85 -0
- package/DEPLOY.md +367 -0
- package/Dockerfile +27 -0
- package/archive/dataCrypto.js +124 -0
- package/archive/delte.js +29 -0
- package/archive/edit.js +29 -0
- package/axios-interceptor-example.js +57 -0
- package/axios-interceptor.js +191 -0
- package/cli.js +313 -0
- package/design-output.txt +5 -0
- package/design-system/virtual-host-management/MASTER.md +202 -0
- package/design-system-output.md +5 -0
- package/design-system-result.md +50 -0
- package/design-system.md +0 -0
- package/docker-compose.yml +18 -0
- package/index.js +404 -0
- package/jsencrypt.min.js +2 -0
- package/modem-api.js +419 -0
- package/package.json +42 -0
- package/parser.js +146 -0
- package/public/index.html +656 -0
- package/readme.md +1 -0
- package/server.js +268 -0
- package/test.js +7 -0
package/.dockerignore
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Build and Push Docker Image
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
env:
|
|
9
|
+
REGISTRY: ghcr.io
|
|
10
|
+
IMAGE_NAME: ${{ github.repository }}
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build-and-push:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
packages: write
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- name: Checkout repository
|
|
21
|
+
uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Set up Docker Buildx
|
|
24
|
+
uses: docker/setup-buildx-action@v3
|
|
25
|
+
|
|
26
|
+
- name: Log in to GitHub Container Registry
|
|
27
|
+
uses: docker/login-action@v3
|
|
28
|
+
with:
|
|
29
|
+
registry: ${{ env.REGISTRY }}
|
|
30
|
+
username: ${{ github.actor }}
|
|
31
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
32
|
+
|
|
33
|
+
- name: Extract metadata (tags, labels)
|
|
34
|
+
id: meta
|
|
35
|
+
uses: docker/metadata-action@v5
|
|
36
|
+
with:
|
|
37
|
+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
38
|
+
tags: |
|
|
39
|
+
type=ref,event=branch
|
|
40
|
+
type=sha,prefix={{branch}}-
|
|
41
|
+
type=raw,value=latest,enable={{is_default_branch}}
|
|
42
|
+
|
|
43
|
+
- name: Build and push Docker image
|
|
44
|
+
uses: docker/build-push-action@v5
|
|
45
|
+
with:
|
|
46
|
+
context: .
|
|
47
|
+
platforms: linux/amd64,linux/arm64
|
|
48
|
+
push: true
|
|
49
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
50
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
51
|
+
cache-from: type=gha
|
|
52
|
+
cache-to: type=gha,mode=max
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
This is a modem/router virtual host (port forwarding) management system for China Unicom modems. It provides both a REST API server and a CLI tool to manage port forwarding rules on the modem at 192.168.1.1.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install dependencies
|
|
13
|
+
pnpm install
|
|
14
|
+
|
|
15
|
+
# Start the API server (with nodemon for auto-reload)
|
|
16
|
+
pnpm start
|
|
17
|
+
|
|
18
|
+
# Run the HTML parser directly
|
|
19
|
+
pnpm run parse
|
|
20
|
+
|
|
21
|
+
# Fetch page from modem
|
|
22
|
+
pnpm run fetch
|
|
23
|
+
|
|
24
|
+
# CLI tool for listing port mappings
|
|
25
|
+
node cli.js list
|
|
26
|
+
node cli.js list -v # verbose mode
|
|
27
|
+
|
|
28
|
+
# CLI tool for adding port forwarding (external port = internal port)
|
|
29
|
+
node cli.js add <ip> <port> <name>
|
|
30
|
+
# Example: node cli.js add 192.168.1.100 8080 "Web Server"
|
|
31
|
+
|
|
32
|
+
# CLI tool for deleting port forwarding by index
|
|
33
|
+
node cli.js del <index>
|
|
34
|
+
# Example: node cli.js del 0
|
|
35
|
+
|
|
36
|
+
# Disable proxy for debugging
|
|
37
|
+
ENABLE_PROXY=false node cli.js list
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Architecture
|
|
41
|
+
|
|
42
|
+
### Core Modules
|
|
43
|
+
|
|
44
|
+
- **server.js** - Express REST API server (port 3000) providing CRUD endpoints for virtual host configurations
|
|
45
|
+
- **modem-api.js** - Modem communication layer handling authentication, encryption (RSA + AES-256-CBC), and configuration submission
|
|
46
|
+
- **index.js** - Login/session management with cookie caching, handles modem authentication flow
|
|
47
|
+
- **parser.js** - Parses modem HTML responses to extract virtual host configurations
|
|
48
|
+
- **cli.js** - Commander-based CLI tool for listing configurations
|
|
49
|
+
- **axios-interceptor.js** - Custom axios instance with request/response logging
|
|
50
|
+
|
|
51
|
+
### Data Flow
|
|
52
|
+
|
|
53
|
+
1. **Authentication**: `index.js` handles login to modem using SHA256 password hashing with random number
|
|
54
|
+
2. **Session Management**: Cookies and session tokens are cached in `.cookie_cache.json` (30 min expiry)
|
|
55
|
+
3. **API Encryption**: `modem-api.js` encrypts requests using RSA (for AES key exchange) + AES-256-CBC (for parameters)
|
|
56
|
+
4. **HTML Parsing**: Modem returns HTML with `Transfer_meaning()` calls containing config data, parsed by `parser.js`
|
|
57
|
+
|
|
58
|
+
### Key Files
|
|
59
|
+
|
|
60
|
+
- `virtual_hosts.json` - Local cache of port forwarding configurations
|
|
61
|
+
- `.cookie_cache.json` - Session cookie and token cache
|
|
62
|
+
- `.cache/page.html` - Cached HTML response from modem
|
|
63
|
+
|
|
64
|
+
### API Endpoints
|
|
65
|
+
|
|
66
|
+
- `GET /api/hosts` - List all virtual hosts (refreshes from modem)
|
|
67
|
+
- `GET /api/hosts/:id` - Get single host by index
|
|
68
|
+
- `POST /api/hosts` - Add new port forwarding rule
|
|
69
|
+
- `PUT /api/hosts/:id` - Update existing rule
|
|
70
|
+
- `DELETE /api/hosts/:id` - Delete rule
|
|
71
|
+
- `POST /api/hosts/refresh` - Force refresh from modem
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
Modem credentials are hardcoded in `index.js` and `modem-api.js`:
|
|
76
|
+
- Base URL: `http://192.168.1.1`
|
|
77
|
+
- Account type: `admin`
|
|
78
|
+
|
|
79
|
+
## Docker
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
docker-compose up -d
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Runs on host network mode with 128MB memory limit.
|
package/DEPLOY.md
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# OpenWrt Docker 部署指南(直接在 OpenWrt 上操作)
|
|
2
|
+
|
|
3
|
+
## 前置要求
|
|
4
|
+
|
|
5
|
+
### 1. 在 OpenWrt 上安装 Docker
|
|
6
|
+
|
|
7
|
+
SSH 登录到你的 OpenWrt 路由器:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
ssh root@192.168.1.1 # 替换为你的路由器 IP
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
安装 Docker:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 更新软件包列表
|
|
17
|
+
opkg update
|
|
18
|
+
|
|
19
|
+
# 安装 Docker(注意:需要足够的存储空间,建议至少 500MB 可用空间)
|
|
20
|
+
opkg install dockerd
|
|
21
|
+
|
|
22
|
+
# 启动 Docker 服务
|
|
23
|
+
/etc/init.d/dockerd start
|
|
24
|
+
/etc/init.d/dockerd enable
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. 检查 Docker 是否正常运行
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
docker --version
|
|
31
|
+
docker info
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 部署步骤(推荐方案)
|
|
35
|
+
|
|
36
|
+
### 步骤 1:上传项目文件到 OpenWrt
|
|
37
|
+
|
|
38
|
+
在你的电脑上打包项目(排除不必要的文件):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Windows PowerShell
|
|
42
|
+
tar -czf admin.tar.gz --exclude=node_modules --exclude=.git --exclude=archive --exclude=design-system .
|
|
43
|
+
|
|
44
|
+
# 或者使用 7-Zip 等工具手动打包
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
上传到 OpenWrt:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# 使用 SCP 上传(替换 IP 地址)
|
|
51
|
+
scp admin.tar.gz root@192.168.1.1:/root/
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 步骤 2:在 OpenWrt 上解压并构建
|
|
55
|
+
|
|
56
|
+
SSH 登录到 OpenWrt 后:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# 创建项目目录
|
|
60
|
+
mkdir -p /root/admin
|
|
61
|
+
cd /root
|
|
62
|
+
|
|
63
|
+
# 解压项目文件
|
|
64
|
+
tar -xzf admin.tar.gz -C /root/admin
|
|
65
|
+
|
|
66
|
+
# 进入项目目录
|
|
67
|
+
cd /root/admin
|
|
68
|
+
|
|
69
|
+
# 查看文件是否正确
|
|
70
|
+
ls -la
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 步骤 3:构建 Docker 镜像
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# 构建镜像(这个过程可能需要 5-15 分钟,取决于设备性能)
|
|
77
|
+
docker build -t modem-admin .
|
|
78
|
+
|
|
79
|
+
# 查看构建的镜像
|
|
80
|
+
docker images
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**注意**:如果构建过程中出现内存不足错误,请参考下面的"性能优化建议"部分。
|
|
84
|
+
|
|
85
|
+
### 步骤 4:运行容器
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# 运行容器
|
|
89
|
+
docker run -d \
|
|
90
|
+
--name modem-admin \
|
|
91
|
+
--restart unless-stopped \
|
|
92
|
+
--network host \
|
|
93
|
+
-v /root/admin/virtual_hosts.json:/app/virtual_hosts.json \
|
|
94
|
+
-v /root/admin/.cookie_cache.json:/app/.cookie_cache.json \
|
|
95
|
+
-v /root/admin/.cache:/app/.cache \
|
|
96
|
+
-m 128m \
|
|
97
|
+
--cpus 0.5 \
|
|
98
|
+
modem-admin
|
|
99
|
+
|
|
100
|
+
# 查看容器状态
|
|
101
|
+
docker ps
|
|
102
|
+
|
|
103
|
+
# 查看日志
|
|
104
|
+
docker logs -f modem-admin
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 步骤 5:验证服务
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# 检查服务是否正常运行
|
|
111
|
+
curl http://localhost:3000/api/hosts
|
|
112
|
+
|
|
113
|
+
# 或者在浏览器访问
|
|
114
|
+
# http://你的路由器IP:3000
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 性能优化建议
|
|
118
|
+
|
|
119
|
+
### 如果构建过程中内存不足
|
|
120
|
+
|
|
121
|
+
OpenWrt 设备通常内存较小,构建 Docker 镜像时可能会遇到内存不足的问题。解决方案:
|
|
122
|
+
|
|
123
|
+
#### 方案 1:增加 swap 空间
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# 创建 512MB swap 文件
|
|
127
|
+
dd if=/dev/zero of=/root/swapfile bs=1M count=512
|
|
128
|
+
chmod 600 /root/swapfile
|
|
129
|
+
mkswap /root/swapfile
|
|
130
|
+
swapon /root/swapfile
|
|
131
|
+
|
|
132
|
+
# 验证 swap 是否生效
|
|
133
|
+
free -m
|
|
134
|
+
|
|
135
|
+
# 构建完成后可以关闭 swap
|
|
136
|
+
swapoff /root/swapfile
|
|
137
|
+
rm /root/swapfile
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### 方案 2:使用预构建镜像(推荐)
|
|
141
|
+
|
|
142
|
+
如果你有其他安装了 Docker 的设备(朋友的电脑、云服务器等),可以:
|
|
143
|
+
|
|
144
|
+
1. 在其他设备上构建镜像
|
|
145
|
+
2. 导出镜像文件
|
|
146
|
+
3. 传输到 OpenWrt
|
|
147
|
+
4. 导入并运行
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# 在其他设备上
|
|
151
|
+
docker build -t modem-admin .
|
|
152
|
+
docker save modem-admin | gzip > modem-admin.tar.gz
|
|
153
|
+
|
|
154
|
+
# 传输到 OpenWrt
|
|
155
|
+
scp modem-admin.tar.gz root@192.168.1.1:/root/
|
|
156
|
+
|
|
157
|
+
# 在 OpenWrt 上导入
|
|
158
|
+
gunzip -c /root/modem-admin.tar.gz | docker load
|
|
159
|
+
|
|
160
|
+
# 运行容器
|
|
161
|
+
cd /root/admin
|
|
162
|
+
docker run -d \
|
|
163
|
+
--name modem-admin \
|
|
164
|
+
--restart unless-stopped \
|
|
165
|
+
--network host \
|
|
166
|
+
-v /root/admin/virtual_hosts.json:/app/virtual_hosts.json \
|
|
167
|
+
-v /root/admin/.cookie_cache.json:/app/.cookie_cache.json \
|
|
168
|
+
-v /root/admin/.cache:/app/.cache \
|
|
169
|
+
-m 128m \
|
|
170
|
+
--cpus 0.5 \
|
|
171
|
+
modem-admin
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 镜像优化说明
|
|
175
|
+
|
|
176
|
+
- **镜像大小**:使用 Alpine Linux,最终镜像约 50-80MB
|
|
177
|
+
- **内存限制**:限制为 128MB,适合低内存设备
|
|
178
|
+
- **CPU 限制**:限制为 0.5 核心,避免占用过多资源
|
|
179
|
+
- **网络模式**:使用 host 模式,减少网络开销
|
|
180
|
+
- **生产依赖**:只安装生产环境依赖,减少体积
|
|
181
|
+
|
|
182
|
+
## 常用管理命令
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
# 查看容器状态
|
|
186
|
+
docker ps
|
|
187
|
+
|
|
188
|
+
# 查看容器详细信息
|
|
189
|
+
docker inspect modem-admin
|
|
190
|
+
|
|
191
|
+
# 查看日志(实时)
|
|
192
|
+
docker logs -f modem-admin
|
|
193
|
+
|
|
194
|
+
# 查看最近 100 行日志
|
|
195
|
+
docker logs --tail 100 modem-admin
|
|
196
|
+
|
|
197
|
+
# 停止服务
|
|
198
|
+
docker stop modem-admin
|
|
199
|
+
|
|
200
|
+
# 启动服务
|
|
201
|
+
docker start modem-admin
|
|
202
|
+
|
|
203
|
+
# 重启服务
|
|
204
|
+
docker restart modem-admin
|
|
205
|
+
|
|
206
|
+
# 删除容器(需要先停止)
|
|
207
|
+
docker stop modem-admin
|
|
208
|
+
docker rm modem-admin
|
|
209
|
+
|
|
210
|
+
# 删除镜像
|
|
211
|
+
docker rmi modem-admin
|
|
212
|
+
|
|
213
|
+
# 查看资源占用
|
|
214
|
+
docker stats modem-admin
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## 访问服务
|
|
218
|
+
|
|
219
|
+
服务启动后,在浏览器访问:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
http://你的路由器IP:3000
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
例如:`http://192.168.1.1:3000`
|
|
226
|
+
|
|
227
|
+
## 故障排查
|
|
228
|
+
|
|
229
|
+
### 1. Docker 服务未启动
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
# 检查 Docker 服务状态
|
|
233
|
+
/etc/init.d/dockerd status
|
|
234
|
+
|
|
235
|
+
# 启动 Docker 服务
|
|
236
|
+
/etc/init.d/dockerd start
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### 2. 存储空间不足
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
# 查看磁盘空间
|
|
243
|
+
df -h
|
|
244
|
+
|
|
245
|
+
# 清理 Docker 缓存
|
|
246
|
+
docker system prune -a
|
|
247
|
+
|
|
248
|
+
# 删除未使用的镜像
|
|
249
|
+
docker image prune -a
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### 3. 内存不足
|
|
253
|
+
|
|
254
|
+
如果设备内存极小(<256MB),可以进一步降低限制:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
docker update --memory 64m modem-admin
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### 4. 容器无法启动
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# 查看详细错误日志
|
|
264
|
+
docker logs modem-admin
|
|
265
|
+
|
|
266
|
+
# 进入容器调试
|
|
267
|
+
docker exec -it modem-admin sh
|
|
268
|
+
|
|
269
|
+
# 检查端口是否被占用
|
|
270
|
+
netstat -tuln | grep 3000
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### 5. 无法访问服务
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# 检查容器是否运行
|
|
277
|
+
docker ps
|
|
278
|
+
|
|
279
|
+
# 检查防火墙规则
|
|
280
|
+
iptables -L -n | grep 3000
|
|
281
|
+
|
|
282
|
+
# 测试本地访问
|
|
283
|
+
curl http://localhost:3000/api/hosts
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### 6. 构建失败
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
# 查看构建日志
|
|
290
|
+
docker build -t modem-admin . --progress=plain
|
|
291
|
+
|
|
292
|
+
# 如果是网络问题,可以配置镜像加速
|
|
293
|
+
# 编辑 /etc/docker/daemon.json
|
|
294
|
+
{
|
|
295
|
+
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
# 重启 Docker
|
|
299
|
+
/etc/init.d/dockerd restart
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## 开机自启动
|
|
303
|
+
|
|
304
|
+
Docker 容器已配置 `--restart unless-stopped`,会在系统重启后自动启动。
|
|
305
|
+
|
|
306
|
+
确保 Docker 服务开机自启:
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
/etc/init.d/dockerd enable
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
验证开机自启是否配置成功:
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
ls /etc/rc.d/ | grep dockerd
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## 更新服务
|
|
319
|
+
|
|
320
|
+
当代码有更新时:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
# 停止并删除旧容器
|
|
324
|
+
docker stop modem-admin
|
|
325
|
+
docker rm modem-admin
|
|
326
|
+
|
|
327
|
+
# 删除旧镜像
|
|
328
|
+
docker rmi modem-admin
|
|
329
|
+
|
|
330
|
+
# 上传新代码并重新构建
|
|
331
|
+
cd /root/admin
|
|
332
|
+
# ... 上传新文件 ...
|
|
333
|
+
docker build -t modem-admin .
|
|
334
|
+
|
|
335
|
+
# 运行新容器
|
|
336
|
+
docker run -d \
|
|
337
|
+
--name modem-admin \
|
|
338
|
+
--restart unless-stopped \
|
|
339
|
+
--network host \
|
|
340
|
+
-v /root/admin/virtual_hosts.json:/app/virtual_hosts.json \
|
|
341
|
+
-v /root/admin/.cookie_cache.json:/app/.cookie_cache.json \
|
|
342
|
+
-v /root/admin/.cache:/app/.cache \
|
|
343
|
+
-m 128m \
|
|
344
|
+
--cpus 0.5 \
|
|
345
|
+
modem-admin
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## 卸载
|
|
349
|
+
|
|
350
|
+
完全卸载服务和 Docker:
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
# 停止并删除容器
|
|
354
|
+
docker stop modem-admin
|
|
355
|
+
docker rm modem-admin
|
|
356
|
+
|
|
357
|
+
# 删除镜像
|
|
358
|
+
docker rmi modem-admin
|
|
359
|
+
|
|
360
|
+
# 删除项目文件
|
|
361
|
+
rm -rf /root/admin
|
|
362
|
+
|
|
363
|
+
# 卸载 Docker(可选)
|
|
364
|
+
/etc/init.d/dockerd stop
|
|
365
|
+
/etc/init.d/dockerd disable
|
|
366
|
+
opkg remove dockerd
|
|
367
|
+
```
|
package/Dockerfile
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# 使用 Alpine Linux 最小化镜像大小
|
|
2
|
+
FROM node:20-alpine
|
|
3
|
+
|
|
4
|
+
# 设置工作目录
|
|
5
|
+
WORKDIR /app
|
|
6
|
+
|
|
7
|
+
# 只复制依赖文件
|
|
8
|
+
COPY package.json pnpm-lock.yaml ./
|
|
9
|
+
|
|
10
|
+
# 安装 pnpm 并安装生产依赖
|
|
11
|
+
RUN npm install -g pnpm && \
|
|
12
|
+
pnpm install --prod --frozen-lockfile && \
|
|
13
|
+
npm cache clean --force && \
|
|
14
|
+
pnpm store prune
|
|
15
|
+
|
|
16
|
+
# 复制应用代码
|
|
17
|
+
COPY index.js server.js parser.js modem-api.js jsencrypt.min.js axios-interceptor.js ./
|
|
18
|
+
COPY public ./public
|
|
19
|
+
|
|
20
|
+
# 创建缓存目录
|
|
21
|
+
RUN mkdir -p .cache
|
|
22
|
+
|
|
23
|
+
# 暴露端口
|
|
24
|
+
EXPOSE 3000
|
|
25
|
+
|
|
26
|
+
# 启动服务
|
|
27
|
+
CMD ["node", "server.js"]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
// 1. 定义从父页面获取的公钥
|
|
4
|
+
const pubKey = `-----BEGIN PUBLIC KEY-----
|
|
5
|
+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAodPTerkUVCYmv28SOfRV
|
|
6
|
+
7UKHVujx/HjCUTAWy9l0L5H0JV0LfDudTdMNPEKloZsNam3YrtEnq6jqMLJV4ASb
|
|
7
|
+
1d6axmIgJ636wyTUS99gj4BKs6bQSTUSE8h/QkUYv4gEIt3saMS0pZpd90y6+B/9
|
|
8
|
+
hZxZE/RKU8e+zgRqp1/762TB7vcjtjOwXRDEL0w71Jk9i8VUQ59MR1Uj5E8X3WIc
|
|
9
|
+
fYSK5RWBkMhfaTRM6ozS9Bqhi40xlSOb3GBxCmliCifOJNLoO9kFoWgAIw5hkSIb
|
|
10
|
+
GH+4Csop9Uy8VvmmB+B3ubFLN35qIa5OG5+SDXn4L7FeAA5lRiGxRi8tsWrtew8w
|
|
11
|
+
nwIDAQAB
|
|
12
|
+
-----END PUBLIC KEY-----`;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 生成 16 位随机字符串
|
|
16
|
+
*/
|
|
17
|
+
function generateRandomString(len = 16) {
|
|
18
|
+
const chars = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
|
|
19
|
+
let str = "";
|
|
20
|
+
for (let i = 0; i < len; i++) {
|
|
21
|
+
str += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
22
|
+
}
|
|
23
|
+
return str;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 对应 EncodeKey(key, iv)
|
|
28
|
+
* 使用 RSA 公钥加密 "key+iv"
|
|
29
|
+
*/
|
|
30
|
+
function encodeKey(key, iv, publicKey) {
|
|
31
|
+
const data = Buffer.from(key + "+" + iv);
|
|
32
|
+
const encrypted = crypto.publicEncrypt(
|
|
33
|
+
{
|
|
34
|
+
key: publicKey,
|
|
35
|
+
padding: crypto.constants.RSA_PKCS1_PADDING, // JSEncrypt 默认填充
|
|
36
|
+
},
|
|
37
|
+
data
|
|
38
|
+
);
|
|
39
|
+
return encrypted.toString('base64');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 处理 ZeroPadding (Node.js 原生不直接支持 ZeroPadding,需要手动处理数据长度)
|
|
44
|
+
*/
|
|
45
|
+
function zeroPad(data, blockSize) {
|
|
46
|
+
const buffer = Buffer.from(data, 'utf8');
|
|
47
|
+
const padLen = blockSize - (buffer.length % blockSize);
|
|
48
|
+
if (padLen === blockSize) return buffer;
|
|
49
|
+
return Buffer.concat([buffer, Buffer.alloc(padLen, 0)]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 对应 EncodePara(data, key, iv)
|
|
54
|
+
* 1. 对 key 和 iv 进行 SHA256
|
|
55
|
+
* 2. 使用 AES-256-CBC 加密
|
|
56
|
+
* 3. 填充模式为 ZeroPadding
|
|
57
|
+
*/
|
|
58
|
+
function encodePara(data, key, iv) {
|
|
59
|
+
// 步骤1: 哈希处理 Key 和 IV
|
|
60
|
+
// 注意:CryptoJS.SHA256 返回的是 256 位(32字节),所以 AES 会自动识别为 AES-256
|
|
61
|
+
const hashedKey = crypto.createHash('sha256').update(key).digest();
|
|
62
|
+
const hashedIv = crypto.createHash('sha256').update(iv).digest();
|
|
63
|
+
|
|
64
|
+
// 步骤2: AES-256-CBC 加密
|
|
65
|
+
// CryptoJS 在 IV 过长时会取前 16 字节 (128bit)
|
|
66
|
+
const realIv = hashedIv.slice(0, 16);
|
|
67
|
+
|
|
68
|
+
// 手动处理 ZeroPadding
|
|
69
|
+
const paddedData = zeroPad(data, 16);
|
|
70
|
+
|
|
71
|
+
const cipher = crypto.createCipheriv('aes-256-cbc', hashedKey, realIv);
|
|
72
|
+
cipher.setAutoPadding(false); // 关闭默认的 PKCS7,使用我们手动做的 ZeroPadding
|
|
73
|
+
|
|
74
|
+
let encrypted = cipher.update(paddedData, null, 'base64');
|
|
75
|
+
encrypted += cipher.final('base64');
|
|
76
|
+
|
|
77
|
+
return encrypted;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 对应 DecodePara (用于解析服务器返回的加密数据)
|
|
82
|
+
*/
|
|
83
|
+
function decodePara(encryptedStr, key, iv) {
|
|
84
|
+
const hashedKey = crypto.createHash('sha256').update(key).digest();
|
|
85
|
+
const hashedIv = crypto.createHash('sha256').update(iv).digest().slice(0, 16);
|
|
86
|
+
|
|
87
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', hashedKey, hashedIv);
|
|
88
|
+
decipher.setAutoPadding(false);
|
|
89
|
+
|
|
90
|
+
let decrypted = decipher.update(encryptedStr, 'base64', 'utf8');
|
|
91
|
+
decrypted += decipher.final('utf8');
|
|
92
|
+
|
|
93
|
+
// 移除 ZeroPadding 产生的末尾空字符
|
|
94
|
+
return decrypted.replace(/\0+$/, '');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// --- 使用示例 ---
|
|
98
|
+
function setDecodePara() {
|
|
99
|
+
var tokenObj = getObj("_SESSION_TOKEN");
|
|
100
|
+
if (tokenObj == null) {
|
|
101
|
+
document.key = null;
|
|
102
|
+
document.iv = null;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
var key = tokenObj.value;
|
|
106
|
+
var iv = key.substr(5, 10);
|
|
107
|
+
document.key = key;
|
|
108
|
+
document.iv = iv;
|
|
109
|
+
}
|
|
110
|
+
const myKey = generateRandomString(16);
|
|
111
|
+
const myIv = generateRandomString(16);
|
|
112
|
+
const rawData = "IF_ACTION=Apply&IF_ERRORSTR=SUCC&IF_ERRORPARAM=SUCC"; // 示例提交参数
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
// 实际编辑接口的参数 'ViewName=NULL&WANCViewName=IGD.WD1.WCD2.WCPPP1&WANCName=NULL&Enable=1&Protocol=2&Name=%E8%BF%9C%E7%A8%8B&MinExtPort=6565&MaxExtPort=6565&InternalHost=192.168.1.17&MinIntPort=6565&MaxIntPort=6565&Description=NULL&LeaseDuration=NULL&PortMappCreator=NULL&MinRemoteHost=0.0.0.0&MaxRemoteHost=0.0.0.0&InternalMacHost=NULL&MacEnable=0&IF_ACTION=apply&IF_ERRORSTR=SUCC&IF_ERRORPARAM=SUCC&IF_ERRORTYPE=-1&IF_INDEX=2&IF_INSTNUM=10&ViewName0=IGD.WD1.WCD2.WCPPP1.FWPM1&WANCViewName0=IGD.WD1.WCD2.WCPPP1&WANCName0=2_INTERNET_R_VID_637&Enable0=1&Protocol0=2&Name0=%E8%B7%AF%E7%94%B1%E5%99%A8&MinExtPort0=5700&MaxExtPort0=5700&InternalHost0=192.168.1.125&MinIntPort0=5700&MaxIntPort0=5700&Description0=&LeaseDuration0=0&PortMappCreator0=&MinRemoteHost0=0.0.0.0&MaxRemoteHost0=0.0.0.0&InternalMacHost0=e0%3Ab6%3A68%3A2e%3A2d%3A5b&MacEnable0=0&WANCName0=&ViewName1=IGD.WD1.WCD2.WCPPP1.FWPM2&WANCViewName1=IGD.WD1.WCD2.WCPPP1&WANCName1=2_INTERNET_R_VID_637&Enable1=1&Protocol1=2&Name1=route&MinExtPort1=23831&MaxExtPort1=23831&InternalHost1=192.168.1.125&MinIntPort1=23831&MaxIntPort1=23831&Description1=&LeaseDuration1=0&PortMappCreator1=&MinRemoteHost1=0.0.0.0&MaxRemoteHost1=0.0.0.0&InternalMacHost1=d8%3Ac8%3Ae9%3A1b%3A0a%3A79&MacEnable1=0&WANCName1=&ViewName2=IGD.WD1.WCD2.WCPPP1.FWPM3&WANCViewName2=IGD.WD1.WCD2.WCPPP1&WANCName2=2_INTERNET_R_VID_637&Enable2=0&Protocol2=2&Name2=%E8%BF%9C%E7%A8%8B&MinExtPort2=6565&MaxExtPort2=6565&InternalHost2=192.168.1.17&MinIntPort2=6565&MaxIntPort2=6565&Description2=&LeaseDuration2=0&PortMappCreator2=&MinRemoteHost2=0.0.0.0&MaxRemoteHost2=0.0.0.0&InternalMacHost2=c8%3A7f%3A54%3A70%3Aee%3Abc&MacEnable2=0&WANCName2=&ViewName3=IGD.WD1.WCD2.WCPPP1.FWPM4&WANCViewName3=IGD.WD1.WCD2.WCPPP1&WANCName3=2_INTERNET_R_VID_637&Enable3=1&Protocol3=0&Name3=tv&MinExtPort3=4567&MaxExtPort3=4567&InternalHost3=192.168.1.125&MinIntPort3=4567&MaxIntPort3=4567&Description3=&LeaseDuration3=0&PortMappCreator3=&MinRemoteHost3=0.0.0.0&MaxRemoteHost3=0.0.0.0&InternalMacHost3=00%3A00%3A00%3A00%3A00%3A00&MacEnable3=0&WANCName3=&ViewName4=IGD.WD1.WCD2.WCPPP1.FWPM6&WANCViewName4=IGD.WD1.WCD2.WCPPP1&WANCName4=2_INTERNET_R_VID_637&Enable4=1&Protocol4=0&Name4=panso&MinExtPort4=7123&MaxExtPort4=7123&InternalHost4=192.168.1.125&MinIntPort4=7123&MaxIntPort4=7123&Description4=&LeaseDuration4=0&PortMappCreator4=&MinRemoteHost4=0.0.0.0&MaxRemoteHost4=0.0.0.0&InternalMacHost4=00%3A00%3A00%3A00%3A00%3A00&MacEnable4=0&WANCName4=&ViewName5=IGD.WD1.WCD2.WCPPP1.FWPM7&WANCViewName5=IGD.WD1.WCD2.WCPPP1&WANCName5=2_INTERNET_R_VID_637&Enable5=1&Protocol5=0&Name5=frp&MinExtPort5=7001&MaxExtPort5=7001&InternalHost5=192.168.1.125&MinIntPort5=7001&MaxIntPort5=7001&Description5=&LeaseDuration5=0&PortMappCreator5=&MinRemoteHost5=0.0.0.0&MaxRemoteHost5=0.0.0.0&InternalMacHost5=00%3A00%3A00%3A00%3A00%3A00&MacEnable5=0&WANCName5=&ViewName6=IGD.WD1.WCD2.WCPPP1.FWPM8&WANCViewName6=IGD.WD1.WCD2.WCPPP1&WANCName6=2_INTERNET_R_VID_637&Enable6=1&Protocol6=0&Name6=frp_25000&MinExtPort6=25000&MaxExtPort6=25000&InternalHost6=192.168.1.125&MinIntPort6=25000&MaxIntPort6=25000&Description6=&LeaseDuration6=0&PortMappCreator6=&MinRemoteHost6=0.0.0.0&MaxRemoteHost6=0.0.0.0&InternalMacHost6=00%3A00%3A00%3A00%3A00%3A00&MacEnable6=0&WANCName6=&ViewName7=IGD.WD1.WCD2.WCPPP1.FWPM9&WANCViewName7=IGD.WD1.WCD2.WCPPP1&WANCName7=2_INTERNET_R_VID_637&Enable7=1&Protocol7=0&Name7=v2_33786&MinExtPort7=33786&MaxExtPort7=33786&InternalHost7=192.168.1.125&MinIntPort7=33786&MaxIntPort7=33786&Description7=&LeaseDuration7=0&PortMappCreator7=&MinRemoteHost7=0.0.0.0&MaxRemoteHost7=0.0.0.0&InternalMacHost7=00%3A00%3A00%3A00%3A00%3A00&MacEnable7=0&WANCName7=&ViewName8=IGD.WD1.WCD2.WCPPP1.FWPM10&WANCViewName8=IGD.WD1.WCD2.WCPPP1&WANCName8=2_INTERNET_R_VID_637&Enable8=1&Protocol8=2&Name8=ai&MinExtPort8=7138&MaxExtPort8=7138&InternalHost8=192.168.1.125&MinIntPort8=8317&MaxIntPort8=8317&Description8=&LeaseDuration8=0&PortMappCreator8=&MinRemoteHost8=0.0.0.0&MaxRemoteHost8=0.0.0.0&InternalMacHost8=00%3A00%3A00%3A00%3A00%3A00&MacEnable8=0&WANCName8=&ViewName9=IGD.WD1.WCD2.WCPPP1.FWPM11&WANCViewName9=IGD.WD1.WCD2.WCPPP1&WANCName9=2_INTERNET_R_VID_637&Enable9=1&Protocol9=2&Name9=clash&MinExtPort9=16412&MaxExtPort9=16412&InternalHost9=192.168.1.125&MinIntPort9=16412&MaxIntPort9=16412&Description9=&LeaseDuration9=0&PortMappCreator9=&MinRemoteHost9=0.0.0.0&MaxRemoteHost9=0.0.0.0&InternalMacHost9=00%3A00%3A00%3A00%3A00%3A00&MacEnable9=0&WANCName9=&_SESSION_TOKEN=8IPkBg7krYYsnHID77YNaDKo'
|
|
116
|
+
// 生成发送给接口的两个参数
|
|
117
|
+
const IF_ENCODE = encodeKey(myKey, myIv, pubKey);
|
|
118
|
+
const IF_ENCODEPARAM = encodePara(rawData, myKey, myIv);
|
|
119
|
+
|
|
120
|
+
console.log("Key:", myKey);
|
|
121
|
+
console.log("IV:", myIv);
|
|
122
|
+
console.log("\n--- 请提交以下参数 ---");
|
|
123
|
+
console.log("IF_ENCODE:", IF_ENCODE);
|
|
124
|
+
console.log("IF_ENCODEPARAM:", IF_ENCODEPARAM);
|