feat: 添加远程VHD选择功能和USB设备支持

实现远程VHD选择服务集成,支持从配置文件中读取远程服务URL
添加NX_INS USB设备检测功能,支持从USB设备扫描和替换VHD文件
新增配置文件解析和HTTP客户端请求功能
优化VHD文件管理逻辑,支持关键词匹配和文件替换
This commit is contained in:
Lannamokia
2025-10-19 04:06:47 +08:00
parent e323921ce2
commit a5386e69e5
15 changed files with 1545 additions and 13 deletions
+54 -13
View File
@@ -74,19 +74,44 @@ namespace VHDMounter
{
isProcessing = true;
// 调试:测试特定文件
var testFile = @"C:\SDEZ_1.56.00_20250317134137.vhd";
if (System.IO.File.Exists(testFile))
// 尝试远程获取VHD选择
OnStatusChanged("正在检查远程VHD选择配置...");
string remoteSelectedKeyword = await vhdManager.GetRemoteVHDSelection();
// 检查是否存在NX_INS USB设备
OnStatusChanged("正在检查NX_INS USB设备...");
var nxInsUSB = vhdManager.FindNXInsUSBDrive();
List<string> usbVhdFiles = new List<string>();
if (nxInsUSB != null)
{
bool isValid = vhdManager.IsVHDFileValid(testFile);
OnStatusChanged($"测试文件 {testFile}: {(isValid ? "符合条件" : "不符合条件")}");
await Task.Delay(2000);
OnStatusChanged($"找到NX_INS USB设备: {nxInsUSB.Name}");
// 扫描USB设备中的VHD文件
usbVhdFiles = vhdManager.ScanUSBForVHDFiles(nxInsUSB);
OnStatusChanged($"在USB设备中找到 {usbVhdFiles.Count} 个VHD文件");
}
else
{
OnStatusChanged("未找到NX_INS USB设备,将使用本地VHD文件");
}
// 扫描VHD文件
var vhdFiles = await vhdManager.ScanForVHDFiles();
// 扫描本地VHD文件
var localVhdFiles = await vhdManager.ScanForVHDFiles();
if (vhdFiles.Count == 0)
// 如果找到USB设备和VHD文件,替换本地文件
if (nxInsUSB != null && usbVhdFiles.Count > 0 && localVhdFiles.Count > 0)
{
OnStatusChanged("开始替换本地VHD文件...");
bool replaced = await vhdManager.ReplaceLocalVHDFiles(usbVhdFiles, localVhdFiles);
if (replaced)
{
OnStatusChanged("本地VHD文件已替换,重新扫描本地文件...");
// 重新扫描本地文件
localVhdFiles = await vhdManager.ScanForVHDFiles();
}
}
if (localVhdFiles.Count == 0)
{
OnStatusChanged("未找到符合条件的VHD文件");
OnStatusChanged("请检查文件名是否包含SDEZ、SDHD或SDDT关键词");
@@ -97,16 +122,32 @@ namespace VHDMounter
string selectedVHD;
if (vhdFiles.Count == 1)
// 如果有远程选择的关键词,优先使用
if (!string.IsNullOrWhiteSpace(remoteSelectedKeyword))
{
selectedVHD = vhdFiles[0];
selectedVHD = vhdManager.FindVHDByKeyword(localVhdFiles, remoteSelectedKeyword);
if (selectedVHD != null)
{
OnStatusChanged($"根据远程选择启动VHD: {System.IO.Path.GetFileName(selectedVHD)}");
await ProcessSelectedVHD(selectedVHD);
return;
}
else
{
OnStatusChanged($"远程选择的关键词 '{remoteSelectedKeyword}' 未找到对应VHD文件,将显示选择器");
}
}
if (localVhdFiles.Count == 1)
{
selectedVHD = localVhdFiles[0];
await ProcessSelectedVHD(selectedVHD);
}
else
{
// 显示选择器
availableVHDs = vhdFiles;
ShowVHDSelector(vhdFiles);
availableVHDs = localVhdFiles;
ShowVHDSelector(localVhdFiles);
}
}
catch (Exception ex)
+234
View File
@@ -5,6 +5,10 @@ using System.IO;
using System.Linq;
using System.Management;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Net.Http;
using System.Text.Json;
using System.Text;
namespace VHDMounter
{
@@ -17,6 +21,9 @@ namespace VHDMounter
public event Action<string> StatusChanged;
public event Action<List<string>> VHDFilesFound;
private static readonly HttpClient httpClient = new HttpClient();
private string configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "vhdmonter_config.ini");
// 调试方法:检查特定文件是否符合条件
public bool IsVHDFileValid(string filePath)
{
@@ -45,6 +52,233 @@ namespace VHDMounter
}
}
// 检查是否存在卷标为NX_INS的USB设备
public DriveInfo FindNXInsUSBDrive()
{
try
{
var usbDrives = DriveInfo.GetDrives().Where(d => d.IsReady && d.DriveType == DriveType.Removable);
foreach (var drive in usbDrives)
{
try
{
if (drive.VolumeLabel == "NX_INS")
{
StatusChanged?.Invoke($"找到NX_INS USB设备: {drive.Name}");
return drive;
}
}
catch (Exception ex)
{
Debug.WriteLine($"读取驱动器 {drive.Name} 卷标时出错: {ex.Message}");
}
}
return null;
}
catch (Exception ex)
{
Debug.WriteLine($"查找NX_INS USB设备时出错: {ex.Message}");
return null;
}
}
// 从USB设备中扫描VHD文件
public List<string> ScanUSBForVHDFiles(DriveInfo usbDrive)
{
StatusChanged?.Invoke($"正在扫描USB设备 {usbDrive.Name} 中的VHD文件...");
var vhdFiles = new List<string>();
try
{
var rootFiles = Directory.GetFiles(usbDrive.RootDirectory.FullName, "*.vhd", SearchOption.TopDirectoryOnly)
.Where(f => TARGET_KEYWORDS.Any(keyword => Path.GetFileName(f).ToUpper().Contains(keyword)))
.ToList();
vhdFiles.AddRange(rootFiles);
StatusChanged?.Invoke($"在USB设备中找到 {vhdFiles.Count} 个VHD文件");
foreach (var file in vhdFiles)
{
Debug.WriteLine($"USB中找到VHD文件: {file}");
}
}
catch (Exception ex)
{
Debug.WriteLine($"扫描USB设备 {usbDrive.Name} 时出错: {ex.Message}");
StatusChanged?.Invoke($"扫描USB设备时出错: {ex.Message}");
}
return vhdFiles;
}
// 替换本地VHD文件
public async Task<bool> ReplaceLocalVHDFiles(List<string> usbVhdFiles, List<string> localVhdFiles)
{
if (usbVhdFiles.Count == 0 || localVhdFiles.Count == 0)
return false;
bool anyReplaced = false;
StatusChanged?.Invoke("正在替换本地VHD文件...");
foreach (var usbFile in usbVhdFiles)
{
string usbFileName = Path.GetFileName(usbFile);
string usbKeyword = ExtractKeyword(usbFileName);
if (string.IsNullOrEmpty(usbKeyword))
continue;
// 查找对应关键词的本地文件
var matchingLocalFiles = localVhdFiles.Where(f =>
ExtractKeyword(Path.GetFileName(f)) == usbKeyword).ToList();
if (matchingLocalFiles.Count > 0)
{
foreach (var localFile in matchingLocalFiles)
{
try
{
// 直接删除本地文件
StatusChanged?.Invoke($"正在删除本地文件: {Path.GetFileName(localFile)}");
if (File.Exists(localFile))
File.Delete(localFile);
// 复制USB文件到本地
StatusChanged?.Invoke($"正在用 {Path.GetFileName(usbFile)} 替换 {Path.GetFileName(localFile)}");
File.Copy(usbFile, localFile);
anyReplaced = true;
StatusChanged?.Invoke($"替换完成: {Path.GetFileName(localFile)}");
}
catch (Exception ex)
{
StatusChanged?.Invoke($"替换文件时出错: {ex.Message}");
Debug.WriteLine($"替换文件时出错: {ex.Message}");
}
}
}
}
return anyReplaced;
}
// 提取VHD文件名中的关键词部分
private string ExtractKeyword(string fileName)
{
foreach (var keyword in TARGET_KEYWORDS)
{
if (fileName.ToUpper().Contains(keyword))
return keyword;
}
return string.Empty;
}
// 读取配置文件
private Dictionary<string, string> ReadConfig()
{
var config = new Dictionary<string, string>();
try
{
if (!File.Exists(configFilePath))
{
StatusChanged?.Invoke("配置文件不存在,使用默认设置");
return config;
}
var lines = File.ReadAllLines(configFilePath);
foreach (var line in lines)
{
if (string.IsNullOrWhiteSpace(line) || line.StartsWith(";") || line.StartsWith("["))
continue;
var parts = line.Split('=', 2);
if (parts.Length == 2)
{
config[parts[0].Trim()] = parts[1].Trim();
}
}
}
catch (Exception ex)
{
StatusChanged?.Invoke($"读取配置文件失败: {ex.Message}");
}
return config;
}
// 远程获取VHD选择
public async Task<string> GetRemoteVHDSelection()
{
try
{
var config = ReadConfig();
if (!config.TryGetValue("EnableRemoteSelection", out var enableRemote) ||
!bool.TryParse(enableRemote, out var isEnabled) || !isEnabled)
{
StatusChanged?.Invoke("远程VHD选择功能已禁用");
return null;
}
if (!config.TryGetValue("BootImageSelectUrl", out var url) || string.IsNullOrWhiteSpace(url))
{
StatusChanged?.Invoke("未配置BootImageSelectUrl");
return null;
}
StatusChanged?.Invoke($"正在从远程获取VHD选择: {url}");
var response = await httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
StatusChanged?.Invoke($"远程请求失败: {response.StatusCode}");
return null;
}
var jsonContent = await response.Content.ReadAsStringAsync();
var jsonDoc = JsonDocument.Parse(jsonContent);
if (jsonDoc.RootElement.TryGetProperty("BootImageSelected", out var bootImageElement))
{
var selectedKeyword = bootImageElement.GetString();
StatusChanged?.Invoke($"远程选择的VHD关键词: {selectedKeyword}");
return selectedKeyword;
}
else
{
StatusChanged?.Invoke("远程响应中未找到BootImageSelected字段");
return null;
}
}
catch (Exception ex)
{
StatusChanged?.Invoke($"远程获取VHD选择失败: {ex.Message}");
return null;
}
}
// 根据关键词查找对应的VHD文件
public string FindVHDByKeyword(List<string> vhdFiles, string keyword)
{
if (string.IsNullOrWhiteSpace(keyword) || vhdFiles == null || vhdFiles.Count == 0)
return null;
var matchingFile = vhdFiles.FirstOrDefault(f =>
ExtractKeyword(Path.GetFileName(f)).Equals(keyword, StringComparison.OrdinalIgnoreCase));
if (matchingFile != null)
{
StatusChanged?.Invoke($"找到匹配关键词 '{keyword}' 的VHD文件: {Path.GetFileName(matchingFile)}");
}
else
{
StatusChanged?.Invoke($"未找到匹配关键词 '{keyword}' 的VHD文件");
}
return matchingFile;
}
public async Task<List<string>> ScanForVHDFiles()
{
StatusChanged?.Invoke("正在扫描VHD文件...");
+44
View File
@@ -0,0 +1,44 @@
# Node.js
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# 运行时生成的文件
vhd-config.json
*.log
# 开发工具
.git
.gitignore
README.md
# Docker相关
Dockerfile
docker-compose.yml
.dockerignore
# IDE和编辑器
.vscode
.idea
*.swp
*.swo
*~
# 操作系统
.DS_Store
Thumbs.db
# 临时文件
*.tmp
*.temp
# 测试覆盖率
coverage
# 环境变量文件
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
+116
View File
@@ -0,0 +1,116 @@
# Node.js 依赖
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
# 运行时生成的配置文件
vhd-config.json
# 日志文件
*.log
logs/
# 环境变量文件
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# IDE 和编辑器
.vscode/
.idea/
*.swp
*.swo
*~
# 操作系统生成的文件
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# 临时文件
*.tmp
*.temp
*.bak
*.backup
# 测试覆盖率报告
coverage/
.nyc_output/
# Docker 相关(可选,根据需要调整)
# Dockerfile
# docker-compose.yml
# .dockerignore
# 构建输出
dist/
build/
# 缓存目录
.cache/
.parcel-cache/
# 运行时数据
data/
*.pid
*.seed
*.pid.lock
# 依赖目录
jspm_packages/
# TypeScript 缓存
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env.test
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
# Gatsby files
.cache/
public
# Storybook build outputs
.out
.storybook-out
# Temporary folders
tmp/
temp/
+182
View File
@@ -0,0 +1,182 @@
# Docker 部署故障排除指南
## 常见问题及解决方案
### 1. Docker镜像拉取失败
**问题**: `failed to resolve source metadata for docker.io/library/node:18-alpine`
**原因**: 网络连接问题或Docker Hub访问受限
**解决方案**:
#### 方案A: 配置Docker镜像源(推荐)
```bash
# 创建或编辑Docker配置文件
# Windows: %USERPROFILE%\.docker\daemon.json
# Linux/macOS: /etc/docker/daemon.json
{
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com",
"https://mirror.baidubce.com"
]
}
# 重启Docker服务
# Windows: 重启Docker Desktop
# Linux: sudo systemctl restart docker
```
#### 方案B: 使用国内镜像
修改 `Dockerfile` 第一行:
```dockerfile
# 原始
FROM node:18-alpine
# 替换为
FROM registry.cn-hangzhou.aliyuncs.com/library/node:18-alpine
```
#### 方案C: 使用本地Node.js部署
```bash
npm install
npm start
```
### 2. 端口占用问题
**问题**: `port is already allocated`
**解决方案**:
```bash
# 查看端口占用
netstat -ano | findstr :8080 # Windows
lsof -i :8080 # Linux/macOS
# 停止占用端口的进程
taskkill /PID <PID> /F # Windows
kill -9 <PID> # Linux/macOS
# 或修改端口
docker-compose down
# 编辑 docker-compose.yml 中的端口映射
docker-compose up -d
```
### 3. 权限问题
**问题**: `permission denied`
**解决方案**:
```bash
# Linux/macOS
sudo chown -R $USER:$USER ./config
chmod 755 ./config
# Windows
# 右键config文件夹 -> 属性 -> 安全 -> 编辑权限
```
### 4. 配置文件不持久化
**问题**: 容器重启后配置丢失
**检查**:
```bash
# 确认卷映射
docker-compose ps
docker inspect vhd-select-server
# 检查配置目录
ls -la ./config/
```
**解决方案**:
```bash
# 确保config目录存在
mkdir -p config
# 检查docker-compose.yml中的volumes配置
# 应该包含: - ./config:/app/config
```
### 5. 容器无法启动
**诊断步骤**:
```bash
# 查看容器状态
docker-compose ps
# 查看详细日志
docker-compose logs vhd-select-server
# 进入容器调试
docker-compose exec vhd-select-server sh
# 检查配置
docker-compose config
```
### 6. 网络连接问题
**问题**: 无法访问 http://localhost:8080
**检查**:
```bash
# 确认容器运行状态
docker-compose ps
# 确认端口映射
docker port vhd-select-server
# 测试网络连接
curl http://localhost:8080/api/status
```
## 完全重置
如果遇到无法解决的问题,可以完全重置:
```bash
# 停止并删除所有容器
docker-compose down -v
# 删除本地镜像(如果有)
docker rmi lty271104/vhd-select-server:latest
# 清理Docker缓存
docker system prune -a
# 重新部署
docker-compose up -d
```
## 获取帮助
1. 查看详细日志: `docker-compose logs -f`
2. 检查Docker版本: `docker --version`
3. 检查系统资源: `docker system df`
4. 如果问题持续,请使用本地Node.js部署方式
## 性能优化
### 减少镜像大小
```dockerfile
# 使用多阶段构建
FROM node:18-alpine AS builder
# ... 构建步骤
FROM node:18-alpine AS runtime
# ... 运行时步骤
```
### 加速构建
```bash
# 使用.dockerignore排除不必要文件
# 已包含在项目中
# 利用构建缓存
docker-compose build --parallel
```
+36
View File
@@ -0,0 +1,36 @@
# 使用官方Node.js运行时作为基础镜像
FROM node:18-alpine
# 设置工作目录
WORKDIR /app
# 复制package.json和package-lock.json(如果存在)
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制应用源代码
COPY . .
# 创建配置文件目录
RUN mkdir -p /app/config
# 暴露端口
EXPOSE 8080
# 设置环境变量
ENV NODE_ENV=production
ENV PORT=8080
ENV CONFIG_PATH=/app/config
# 创建非root用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
# 更改文件所有权
RUN chown -R nodejs:nodejs /app
USER nodejs
# 启动应用
CMD ["npm", "start"]
+266
View File
@@ -0,0 +1,266 @@
# VHD选择服务器 - 跨平台版本
一个现代化的跨平台VHD关键词管理服务器,提供Web GUI界面和HTTP API接口。
## ✨ 特性
- 🌐 **跨平台支持**: 支持Windows、Linux、macOS
- 🎨 **现代化Web界面**: 响应式设计,支持移动设备
- 🔧 **RESTful API**: 标准HTTP API接口
- 💾 **持久化存储**: 自动保存配置到JSON文件
- 🚀 **零配置启动**: 开箱即用
- 📱 **实时更新**: 界面自动刷新状态
- 🔒 **输入验证**: 安全的用户输入处理
## 🛠️ 系统要求
### Node.js版本(推荐)
- Node.js 14.0+
- npm(随Node.js安装)
### Python版本(备选)
- Python 3.6+
- 无需额外依赖
## 🚀 快速开始
### 方法1: Docker部署(推荐)
1. **一键部署脚本(最简单)**
```bash
# Windows
deploy.bat
# Linux/macOS
chmod +x deploy.sh
./deploy.sh
```
2. **使用Docker Compose**
```bash
# 构建并启动服务
docker-compose up -d
# 查看日志
docker-compose logs -f
# 停止服务
docker-compose down
```
3. **使用Docker命令**
```bash
# 运行容器(配置持久化)
docker run -d \
--name vhd-select-server \
-p 8080:8080 \
-v $(pwd)/config:/app/config \
lty271104/vhd-select-server:latest
```
### 方法2: Node.js版本
1. **安装Node.js**
- 访问 [https://nodejs.org/](https://nodejs.org/)
- 下载并安装LTS版本
2. **启动服务器**
```bash
# 双击运行
start.bat
# 或者命令行运行
npm install
npm start
```
### 方法3: Python版本
```bash
# 直接运行Python服务器
python server.py
# 或指定端口
python server.py 8080
```
## 🌐 访问界面
服务器启动后,访问以下地址:
- **Web界面**: http://localhost:8080
- **API文档**: http://localhost:8080/api/status
## 📡 API接口
### 获取当前VHD关键词
```http
GET /api/boot-image-select
```
**响应示例**:
```json
{
"success": true,
"BootImageSelected": "SDEZ",
"timestamp": "2025-10-19T03:05:33.486615"
}
```
### 设置VHD关键词
```http
POST /api/set-vhd
Content-Type: application/json
{
"BootImageSelected": "NEW_KEYWORD"
}
```
**响应示例**:
```json
{
"success": true,
"BootImageSelected": "NEW_KEYWORD",
"message": "VHD关键词更新成功"
}
```
### 服务器状态
```http
GET /api/status
```
**响应示例**:
```json
{
"success": true,
"status": "running",
"BootImageSelected": "SDEZ",
"timestamp": "2025-10-19T03:05:33.486615",
"version": "1.0.0"
}
```
## 🎯 使用说明
### Web界面操作
1. **查看当前状态**: 页面会显示服务器运行状态和当前VHD关键词
2. **设置新关键词**: 在输入框中输入新的VHD关键词,点击"更新"按钮
3. **自动刷新**: 页面每30秒自动刷新状态
4. **实时反馈**: 操作结果会立即显示在界面上
### 配置持久化
- **Docker部署**: 配置保存在 `/app/config/vhd-config.json`,通过卷映射到主机的 `./config` 目录
- **本地部署**: 配置保存在项目根目录的 `vhd-config.json` 文件中
- **环境变量**: 可通过 `CONFIG_PATH` 环境变量自定义配置文件路径
### Docker相关
#### 环境变量
- `PORT`: 服务端口(默认: 8080)
- `CONFIG_PATH`: 配置文件目录路径(默认: /app/config)
- `NODE_ENV`: 运行环境(默认: production)
#### 数据卷
- `/app/config`: 配置文件持久化目录
- `/app/data`: 数据文件持久化目录(可选)
#### 健康检查
- 端点: `http://localhost:8080/api/status`
- 间隔: 30秒
- 超时: 10秒
- 重试: 3次
#### 故障排除
如果Docker部署遇到问题,请参考 [Docker故障排除指南](DOCKER_TROUBLESHOOTING.md)
### 命令行测试
```bash
# 获取当前VHD关键词
curl http://localhost:8080/api/boot-image-select
# 设置新的VHD关键词
curl -X POST http://localhost:8080/api/set-vhd \
-H "Content-Type: application/json" \
-d '{"BootImageSelected":"CUSTOM_VHD"}'
```
## 📁 文件结构
```
VHDSelectServer/
├── server.js # Node.js服务器
├── server.py # Python服务器
├── package.json # Node.js依赖配置
├── start.bat # Windows启动脚本
├── public/
│ └── index.html # Web界面
├── vhd-config.json # 配置文件(自动生成)
└── README.md # 说明文档
```
## 🔧 配置说明
- **端口**: 默认8080,可通过环境变量`PORT`修改
- **配置文件**: `vhd-config.json`自动生成,存储当前VHD关键词
- **日志**: 服务器会在控制台输出访问日志
## 🔗 与VHD挂载程序集成
此服务器与原有的VHD挂载程序完全兼容:
1. **API兼容**: 保持原有的`/api/boot-image-select`接口
2. **数据格式**: 返回相同的JSON格式
3. **配置持久化**: VHD关键词会自动保存和恢复
## 🛡️ 安全特性
- **输入验证**: 自动验证和清理用户输入
- **CORS支持**: 支持跨域请求
- **错误处理**: 完善的错误处理和用户反馈
- **格式标准化**: 自动转换为大写格式
## 🐛 故障排除
### 端口被占用
```bash
# 检查端口占用
netstat -ano | findstr :8080
# 使用其他端口
python server.py 8081
```
### Node.js未安装
- 下载安装: https://nodejs.org/
- 使用Python版本作为备选方案
### 权限问题
- 确保有写入配置文件的权限
- 在管理员模式下运行(如需要)
## 📝 更新日志
### v1.0.0
- ✅ 跨平台支持(Node.js + Python)
- ✅ 现代化Web界面
- ✅ RESTful API接口
- ✅ 用户自定义VHD关键词
- ✅ 配置持久化存储
- ✅ 实时状态更新
## 🤝 技术支持
如有问题,请检查:
1. 服务器是否正常启动
2. 端口是否被占用
3. 防火墙设置
4. 浏览器控制台错误信息
---
**🎉 享受使用全新的跨平台VHD选择服务器!**
+49
View File
@@ -0,0 +1,49 @@
@echo off
echo VHD Select Server Docker 部署脚本
echo ================================
echo 检查Docker是否运行...
docker --version >nul 2>&1
if %errorlevel% neq 0 (
echo 错误: Docker未安装或未运行
echo 请先安装并启动Docker Desktop
pause
exit /b 1
)
echo 检查Docker Compose...
docker-compose --version >nul 2>&1
if %errorlevel% neq 0 (
echo 错误: Docker Compose未安装
pause
exit /b 1
)
echo 创建配置目录...
if not exist "config" mkdir config
echo 拉取并启动服务...
docker-compose up -d
if %errorlevel% equ 0 (
echo.
echo ✅ 部署成功!
echo 🌐 Web界面: http://localhost:8080
echo 📁 配置文件: ./config/vhd-config.json
echo.
echo 常用命令:
echo 查看日志: docker-compose logs -f
echo 停止服务: docker-compose down
echo 重启服务: docker-compose restart
) else (
echo.
echo ❌ 部署失败,请检查错误信息
echo 如果是网络问题,请尝试:
echo 1. 检查网络连接
echo 2. 配置Docker镜像源
echo 3. 使用本地Node.js部署: npm start
)
echo.
echo 按任意键退出...
pause >nul
+59
View File
@@ -0,0 +1,59 @@
#!/bin/bash
echo "VHD Select Server Docker 部署脚本"
echo "================================"
# 检查Docker是否安装
if ! command -v docker &> /dev/null; then
echo "❌ 错误: Docker未安装"
echo "请先安装Docker: https://docs.docker.com/get-docker/"
exit 1
fi
# 检查Docker是否运行
if ! docker info &> /dev/null; then
echo "❌ 错误: Docker未运行"
echo "请启动Docker服务"
exit 1
fi
echo "✅ Docker检查通过"
# 检查Docker Compose
if ! command -v docker-compose &> /dev/null; then
echo "❌ 错误: Docker Compose未安装"
echo "请安装Docker Compose"
exit 1
fi
echo "✅ Docker Compose检查通过"
# 创建配置目录
echo "📁 创建配置目录..."
mkdir -p config
# 拉取并启动服务
echo "🚀 拉取并启动服务..."
if docker-compose up -d; then
echo ""
echo "✅ 部署成功!"
echo "🌐 Web界面: http://localhost:8080"
echo "📁 配置文件: ./config/vhd-config.json"
echo ""
echo "常用命令:"
echo " 查看日志: docker-compose logs -f"
echo " 停止服务: docker-compose down"
echo " 重启服务: docker-compose restart"
echo " 查看状态: docker-compose ps"
else
echo ""
echo "❌ 部署失败,请检查错误信息"
echo "如果是网络问题,请尝试:"
echo " 1. 检查网络连接"
echo " 2. 配置Docker镜像源"
echo " 3. 使用本地Node.js部署: npm start"
exit 1
fi
echo ""
echo "🎉 部署完成!"
+28
View File
@@ -0,0 +1,28 @@
version: '3.8'
services:
vhd-select-server:
image: lty271104/vhd-select-server:latest
container_name: vhd-select-server
ports:
- "8080:8080"
volumes:
# 配置文件持久化映射
- ./config:/app/config
# 可选:映射整个数据目录
- vhd-data:/app/data
environment:
- NODE_ENV=production
- PORT=8080
- CONFIG_PATH=/app/config
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/api/status"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
volumes:
vhd-data:
driver: local
+27
View File
@@ -0,0 +1,27 @@
{
"name": "vhd-select-server",
"version": "1.0.0",
"description": "Cross-platform VHD selection server with web GUI",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "node server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"vhd",
"server",
"cross-platform",
"web-gui"
],
"author": "VHD Select Server",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"body-parser": "^1.20.2"
},
"engines": {
"node": ">=14.0.0"
}
}
+152
View File
@@ -0,0 +1,152 @@
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const path = require('path');
const fs = require('fs');
const app = express();
const PORT = process.env.PORT || 8080;
// 中间件
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));
// VHD关键词存储
let currentVhdKeyword = 'SDEZ'; // 默认值
// 配置文件路径 - 支持Docker环境
const configDir = process.env.CONFIG_PATH || __dirname;
const configFile = path.join(configDir, 'vhd-config.json');
// 确保配置目录存在
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
// 加载配置
function loadConfig() {
try {
if (fs.existsSync(configFile)) {
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
currentVhdKeyword = config.vhdKeyword || 'SDEZ';
}
} catch (error) {
console.log('配置文件加载失败,使用默认值:', error.message);
}
}
// 保存配置
function saveConfig() {
try {
const config = { vhdKeyword: currentVhdKeyword };
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
} catch (error) {
console.error('配置文件保存失败:', error.message);
}
}
// API路由
// 获取当前VHD关键词
app.get('/api/boot-image-select', (req, res) => {
console.log(`[${new Date().toISOString()}] API请求: GET /api/boot-image-select`);
res.json({
success: true,
BootImageSelected: currentVhdKeyword,
timestamp: new Date().toISOString()
});
});
// 设置VHD关键词
app.post('/api/set-vhd', (req, res) => {
const { BootImageSelected } = req.body;
if (!BootImageSelected || typeof BootImageSelected !== 'string') {
return res.status(400).json({
success: false,
error: 'VHD关键词不能为空且必须是字符串'
});
}
// 验证关键词格式(可选:添加更严格的验证)
const trimmedKeyword = BootImageSelected.trim().toUpperCase();
if (trimmedKeyword.length === 0) {
return res.status(400).json({
success: false,
error: 'VHD关键词不能为空'
});
}
currentVhdKeyword = trimmedKeyword;
saveConfig();
console.log(`[${new Date().toISOString()}] VHD关键词已更新为: ${currentVhdKeyword}`);
res.json({
success: true,
BootImageSelected: currentVhdKeyword,
message: 'VHD关键词更新成功'
});
});
// 获取服务器状态
app.get('/api/status', (req, res) => {
res.json({
success: true,
status: 'running',
BootImageSelected: currentVhdKeyword,
uptime: process.uptime(),
timestamp: new Date().toISOString(),
version: '1.0.0'
});
});
// 主页路由
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// 404处理
app.use((req, res) => {
res.status(404).json({
success: false,
error: '页面未找到'
});
});
// 错误处理
app.use((err, req, res, next) => {
console.error('服务器错误:', err);
res.status(500).json({
success: false,
error: '服务器内部错误'
});
});
// 启动服务器
loadConfig();
app.listen(PORT, () => {
console.log('='.repeat(50));
console.log('🚀 VHD选择服务器已启动');
console.log(`📡 服务器地址: http://localhost:${PORT}`);
console.log(`🔧 API地址: http://localhost:${PORT}/api/boot-image-select`);
console.log(`📊 状态页面: http://localhost:${PORT}/api/status`);
console.log(`🎯 当前VHD关键词: ${currentVhdKeyword}`);
console.log('='.repeat(50));
});
// 优雅关闭
process.on('SIGINT', () => {
console.log('\n正在关闭服务器...');
saveConfig();
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('\n正在关闭服务器...');
saveConfig();
process.exit(0);
});
+245
View File
@@ -0,0 +1,245 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
VHD选择服务器 - Python版本
跨平台的VHD关键词管理服务器,带有Web GUI界面
"""
import json
import os
import sys
from datetime import datetime
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
import threading
import webbrowser
from pathlib import Path
class VHDSelectHandler(BaseHTTPRequestHandler):
def __init__(self, *args, **kwargs):
self.config_file = Path(__file__).parent / 'vhd-config.json'
super().__init__(*args, **kwargs)
def load_config(self):
"""加载VHD配置"""
try:
if self.config_file.exists():
with open(self.config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
return config.get('vhdKeyword', 'SDEZ')
except Exception as e:
print(f"配置加载失败: {e}")
return 'SDEZ'
def save_config(self, vhd_keyword):
"""保存VHD配置"""
try:
config = {'vhdKeyword': vhd_keyword}
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(config, f, ensure_ascii=False, indent=2)
return True
except Exception as e:
print(f"配置保存失败: {e}")
return False
def send_json_response(self, data, status_code=200):
"""发送JSON响应"""
self.send_response(status_code)
self.send_header('Content-Type', 'application/json; charset=utf-8')
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
self.end_headers()
json_str = json.dumps(data, ensure_ascii=False, indent=2)
self.wfile.write(json_str.encode('utf-8'))
def send_html_file(self, file_path):
"""发送HTML文件"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(content.encode('utf-8'))
except FileNotFoundError:
self.send_error(404, '文件未找到')
except Exception as e:
self.send_error(500, f'服务器错误: {e}')
def do_OPTIONS(self):
"""处理CORS预检请求"""
self.send_response(200)
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
self.end_headers()
def do_GET(self):
"""处理GET请求"""
parsed_path = urlparse(self.path)
path = parsed_path.path
print(f"[{datetime.now().isoformat()}] GET {path}")
if path == '/':
# 主页
html_file = Path(__file__).parent / 'public' / 'index.html'
self.send_html_file(html_file)
elif path == '/api/boot-image-select':
# 获取当前VHD关键词
current_keyword = self.load_config()
response = {
'success': True,
'BootImageSelected': current_keyword,
'timestamp': datetime.now().isoformat()
}
self.send_json_response(response)
elif path == '/api/status':
# 服务器状态
current_keyword = self.load_config()
response = {
'success': True,
'status': 'running',
'BootImageSelected': current_keyword,
'timestamp': datetime.now().isoformat(),
'version': '1.0.0 (Python)'
}
self.send_json_response(response)
else:
# 404错误
response = {
'success': False,
'error': '页面未找到'
}
self.send_json_response(response, 404)
def do_POST(self):
"""处理POST请求"""
parsed_path = urlparse(self.path)
path = parsed_path.path
print(f"[{datetime.now().isoformat()}] POST {path}")
if path == '/api/set-vhd':
try:
# 读取请求体
content_length = int(self.headers.get('Content-Length', 0))
post_data = self.rfile.read(content_length)
# 解析JSON数据
data = json.loads(post_data.decode('utf-8'))
vhd_keyword = data.get('BootImageSelected', '').strip().upper()
if not vhd_keyword:
response = {
'success': False,
'error': 'VHD关键词不能为空'
}
self.send_json_response(response, 400)
return
# 保存配置
if self.save_config(vhd_keyword):
response = {
'success': True,
'BootImageSelected': vhd_keyword,
'message': 'VHD关键词更新成功'
}
print(f"VHD关键词已更新为: {vhd_keyword}")
self.send_json_response(response)
else:
response = {
'success': False,
'error': '配置保存失败'
}
self.send_json_response(response, 500)
except json.JSONDecodeError:
response = {
'success': False,
'error': '无效的JSON数据'
}
self.send_json_response(response, 400)
except Exception as e:
response = {
'success': False,
'error': f'服务器错误: {str(e)}'
}
self.send_json_response(response, 500)
else:
# 404错误
response = {
'success': False,
'error': '页面未找到'
}
self.send_json_response(response, 404)
def log_message(self, format, *args):
"""自定义日志格式"""
pass # 禁用默认日志,使用自定义日志
def start_server(port=8080):
"""启动服务器"""
server_address = ('', port)
httpd = HTTPServer(server_address, VHDSelectHandler)
print('=' * 50)
print('🚀 VHD选择服务器已启动 (Python版本)')
print(f'📡 服务器地址: http://localhost:{port}')
print(f'🔧 API地址: http://localhost:{port}/api/boot-image-select')
print(f'📊 状态页面: http://localhost:{port}/api/status')
# 加载当前配置
config_file = Path(__file__).parent / 'vhd-config.json'
try:
if config_file.exists():
with open(config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
current_keyword = config.get('vhdKeyword', 'SDEZ')
else:
current_keyword = 'SDEZ'
except:
current_keyword = 'SDEZ'
print(f'🎯 当前VHD关键词: {current_keyword}')
print('=' * 50)
print('💡 提示: 按 Ctrl+C 停止服务器')
print()
# 自动打开浏览器
def open_browser():
import time
time.sleep(1) # 等待服务器启动
try:
webbrowser.open(f'http://localhost:{port}')
except:
pass
browser_thread = threading.Thread(target=open_browser)
browser_thread.daemon = True
browser_thread.start()
try:
httpd.serve_forever()
except KeyboardInterrupt:
print('\n正在关闭服务器...')
httpd.shutdown()
print('服务器已关闭')
if __name__ == '__main__':
# 检查命令行参数
port = 8080
if len(sys.argv) > 1:
try:
port = int(sys.argv[1])
except ValueError:
print('错误: 端口号必须是数字')
sys.exit(1)
start_server(port)
+47
View File
@@ -0,0 +1,47 @@
@echo off
chcp 65001 >nul
echo ================================================
echo VHD选择服务器启动脚本
echo ================================================
echo.
:: 检查Node.js是否已安装
node --version >nul 2>&1
if %errorlevel% neq 0 (
echo ❌ 未检测到Node.js,请先安装Node.js
echo.
echo 📥 请访问以下网址下载并安装Node.js:
echo https://nodejs.org/
echo.
echo 💡 建议下载LTS版本(长期支持版本)
echo.
pause
exit /b 1
)
echo ✅ Node.js已安装
node --version
echo.
:: 检查依赖是否已安装
if not exist "node_modules" (
echo 📦 正在安装依赖包...
npm install
if %errorlevel% neq 0 (
echo ❌ 依赖安装失败
pause
exit /b 1
)
echo ✅ 依赖安装完成
echo.
)
echo 🚀 正在启动VHD选择服务器...
echo.
node server.js
if %errorlevel% neq 0 (
echo.
echo ❌ 服务器启动失败
pause
)
+6
View File
@@ -0,0 +1,6 @@
[Settings]
; 是否启用远程VHD选择功能 (true/false)
EnableRemoteSelection=false
; 远程VHD选择服务的URL
BootImageSelectUrl=http://localhost:8080/api/boot-image-select