feat: 支持了服务注册(windows与linux平台)
This commit is contained in:
parent
dcd8945c84
commit
63db36ce01
|
|
@ -0,0 +1,403 @@
|
|||
# 服务注册快速开始
|
||||
|
||||
## 快速测试(非管理员权限)
|
||||
|
||||
### 1. 正常运行模式(不注册服务)
|
||||
|
||||
```powershell
|
||||
# Server
|
||||
cd bin
|
||||
.\server.exe -config config.yaml
|
||||
|
||||
# Client
|
||||
.\client.exe -server localhost:9000
|
||||
```
|
||||
|
||||
## 注册为 Windows 服务(需要管理员权限)
|
||||
|
||||
### Server 服务
|
||||
|
||||
#### 步骤 1: 以管理员身份打开 PowerShell
|
||||
|
||||
右键点击 PowerShell → "以管理员身份运行"
|
||||
|
||||
#### 步骤 2: 进入程序目录
|
||||
|
||||
```powershell
|
||||
cd D:\Develop\Projects\go-tunnel\bin
|
||||
```
|
||||
|
||||
#### 步骤 3: 安装服务
|
||||
|
||||
```powershell
|
||||
.\server.exe -reg install
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```
|
||||
服务 Go Tunnel Server 安装成功
|
||||
可执行文件: D:\Develop\Projects\go-tunnel\bin\server.exe
|
||||
启动参数:
|
||||
工作目录: D:\Develop\Projects\go-tunnel\bin
|
||||
|
||||
使用以下命令管理服务:
|
||||
启动服务: sc start GoTunnelServer
|
||||
停止服务: sc stop GoTunnelServer
|
||||
查询状态: sc query GoTunnelServer
|
||||
卸载服务: D:\Develop\Projects\go-tunnel\bin\server.exe -reg uninstall
|
||||
```
|
||||
|
||||
#### 步骤 4: 启动服务
|
||||
|
||||
```powershell
|
||||
sc start GoTunnelServer
|
||||
```
|
||||
|
||||
或者使用:
|
||||
```powershell
|
||||
.\server.exe -reg start
|
||||
```
|
||||
|
||||
#### 步骤 5: 检查服务状态
|
||||
|
||||
```powershell
|
||||
sc query GoTunnelServer
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```
|
||||
SERVICE_NAME: GoTunnelServer
|
||||
TYPE : 10 WIN32_OWN_PROCESS
|
||||
STATE : 4 RUNNING
|
||||
...
|
||||
```
|
||||
|
||||
#### 步骤 6: 查看服务日志
|
||||
|
||||
打开"事件查看器":
|
||||
1. 按 `Win + R`,输入 `eventvwr.msc`
|
||||
2. 展开 "Windows 日志" → "应用程序"
|
||||
3. 在右侧点击"筛选当前日志"
|
||||
4. 在事件源中搜索相关日志
|
||||
|
||||
或者查看 API 接口:
|
||||
```powershell
|
||||
# 浏览器访问
|
||||
http://localhost:8080
|
||||
```
|
||||
|
||||
### Client 服务
|
||||
|
||||
#### 安装并启动
|
||||
|
||||
```powershell
|
||||
# 以管理员身份运行
|
||||
cd D:\Develop\Projects\go-tunnel\bin
|
||||
|
||||
# 安装服务
|
||||
.\client.exe -server your-server.com:9000 -reg install
|
||||
|
||||
# 启动服务
|
||||
sc start GoTunnelClient
|
||||
|
||||
# 查看状态
|
||||
sc query GoTunnelClient
|
||||
```
|
||||
|
||||
## 管理已安装的服务
|
||||
|
||||
### 启动服务
|
||||
|
||||
```powershell
|
||||
# Server
|
||||
sc start GoTunnelServer
|
||||
|
||||
# Client
|
||||
sc start GoTunnelClient
|
||||
```
|
||||
|
||||
### 停止服务
|
||||
|
||||
```powershell
|
||||
# Server
|
||||
sc stop GoTunnelServer
|
||||
|
||||
# Client
|
||||
sc stop GoTunnelClient
|
||||
```
|
||||
|
||||
### 重启服务
|
||||
|
||||
```powershell
|
||||
# Server
|
||||
sc stop GoTunnelServer
|
||||
sc start GoTunnelServer
|
||||
|
||||
# Client
|
||||
sc stop GoTunnelClient
|
||||
sc start GoTunnelClient
|
||||
```
|
||||
|
||||
### 查看服务状态
|
||||
|
||||
```powershell
|
||||
# Server
|
||||
sc query GoTunnelServer
|
||||
|
||||
# Client
|
||||
sc query GoTunnelClient
|
||||
```
|
||||
|
||||
### 卸载服务
|
||||
|
||||
```powershell
|
||||
# Server
|
||||
.\server.exe -reg uninstall
|
||||
|
||||
# Client
|
||||
.\client.exe -reg uninstall
|
||||
```
|
||||
|
||||
## 使用服务管理器(图形界面)
|
||||
|
||||
### 打开服务管理器
|
||||
|
||||
1. 按 `Win + R`
|
||||
2. 输入 `services.msc`
|
||||
3. 按回车
|
||||
|
||||
### 在服务列表中找到服务
|
||||
|
||||
- **Go Tunnel Server** (GoTunnelServer)
|
||||
- **Go Tunnel Client** (GoTunnelClient)
|
||||
|
||||
### 可用操作
|
||||
|
||||
- 右键点击服务
|
||||
- 选择:启动、停止、重新启动、属性
|
||||
|
||||
### 查看和修改恢复选项
|
||||
|
||||
1. 右键点击服务 → "属性"
|
||||
2. 切换到"恢复"选项卡
|
||||
3. 可以看到和修改失败后的恢复策略:
|
||||
- 第一次失败:重新启动服务(1分钟后)
|
||||
- 第二次失败:重新启动服务(1分钟后)
|
||||
- 后续失败:重新启动服务(1分钟后)
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 问题 1: 服务无法启动
|
||||
|
||||
#### 检查权限
|
||||
|
||||
```powershell
|
||||
# 确认是否以管理员运行
|
||||
[Security.Principal.WindowsPrincipal] $user = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$user.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
# 应该返回 True
|
||||
```
|
||||
|
||||
#### 手动测试运行
|
||||
|
||||
```powershell
|
||||
# 以普通模式运行,查看错误信息
|
||||
.\server.exe -config config.yaml
|
||||
```
|
||||
|
||||
#### 检查端口占用
|
||||
|
||||
```powershell
|
||||
# 检查 API 端口(默认 8080)
|
||||
netstat -ano | findstr :8080
|
||||
|
||||
# 检查隧道端口(默认 9000)
|
||||
netstat -ano | findstr :9000
|
||||
```
|
||||
|
||||
### 问题 2: 服务启动但无法访问
|
||||
|
||||
#### 检查防火墙
|
||||
|
||||
```powershell
|
||||
# 添加防火墙规则
|
||||
New-NetFirewallRule -DisplayName "Go Tunnel Server" -Direction Inbound -Protocol TCP -LocalPort 8080 -Action Allow
|
||||
New-NetFirewallRule -DisplayName "Go Tunnel Server Tunnel" -Direction Inbound -Protocol TCP -LocalPort 9000 -Action Allow
|
||||
```
|
||||
|
||||
#### 测试 API 是否可访问
|
||||
|
||||
```powershell
|
||||
# 使用 curl 测试
|
||||
curl http://localhost:8080
|
||||
|
||||
# 或使用浏览器访问
|
||||
start http://localhost:8080
|
||||
```
|
||||
|
||||
### 问题 3: 配置文件找不到
|
||||
|
||||
服务会在工作目录查找配置文件。确保:
|
||||
|
||||
1. `config.yaml` 在可执行文件同一目录
|
||||
2. 或者安装时指定配置文件路径:
|
||||
|
||||
```powershell
|
||||
.\server.exe -config "C:\full\path\to\config.yaml" -reg install
|
||||
```
|
||||
|
||||
### 问题 4: 数据库权限问题
|
||||
|
||||
确保服务有权限访问数据库文件:
|
||||
|
||||
```powershell
|
||||
# 检查数据库文件权限
|
||||
Get-Acl data\tunnel.db | Format-List
|
||||
|
||||
# 如果需要,添加 SYSTEM 账户的权限
|
||||
$acl = Get-Acl data\tunnel.db
|
||||
$permission = "NT AUTHORITY\SYSTEM","FullControl","Allow"
|
||||
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
|
||||
$acl.SetAccessRule($accessRule)
|
||||
Set-Acl data\tunnel.db $acl
|
||||
```
|
||||
|
||||
## 测试自动重启
|
||||
|
||||
### 模拟服务崩溃
|
||||
|
||||
1. 找到服务进程 ID:
|
||||
|
||||
```powershell
|
||||
Get-Process -Name server | Select-Object Id,ProcessName
|
||||
```
|
||||
|
||||
2. 强制结束进程:
|
||||
|
||||
```powershell
|
||||
Stop-Process -Id <进程ID> -Force
|
||||
```
|
||||
|
||||
3. 等待约 60 秒
|
||||
|
||||
4. 检查服务状态:
|
||||
|
||||
```powershell
|
||||
sc query GoTunnelServer
|
||||
```
|
||||
|
||||
服务应该会自动重启,状态显示为 RUNNING。
|
||||
|
||||
## 完整示例脚本
|
||||
|
||||
### 安装和启动 Server 服务
|
||||
|
||||
```powershell
|
||||
# install_server.ps1
|
||||
# 需要以管理员身份运行
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "检查管理员权限..."
|
||||
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
|
||||
if (-not $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
Write-Error "请以管理员身份运行此脚本"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "进入程序目录..."
|
||||
cd $PSScriptRoot
|
||||
|
||||
Write-Host "停止并卸载旧服务(如果存在)..."
|
||||
sc stop GoTunnelServer 2>$null
|
||||
.\server.exe -reg uninstall 2>$null
|
||||
|
||||
Write-Host "安装新服务..."
|
||||
.\server.exe -reg install
|
||||
|
||||
Write-Host "启动服务..."
|
||||
sc start GoTunnelServer
|
||||
|
||||
Write-Host "等待服务启动..."
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
Write-Host "检查服务状态..."
|
||||
sc query GoTunnelServer
|
||||
|
||||
Write-Host "`n服务安装完成!"
|
||||
Write-Host "API 地址: http://localhost:8080"
|
||||
Write-Host "查看日志: eventvwr.msc"
|
||||
```
|
||||
|
||||
### 卸载服务
|
||||
|
||||
```powershell
|
||||
# uninstall_server.ps1
|
||||
# 需要以管理员身份运行
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "停止服务..."
|
||||
sc stop GoTunnelServer
|
||||
|
||||
Write-Host "等待服务停止..."
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
Write-Host "卸载服务..."
|
||||
.\server.exe -reg uninstall
|
||||
|
||||
Write-Host "服务已卸载"
|
||||
```
|
||||
|
||||
## 监控脚本
|
||||
|
||||
### 检查服务运行状态
|
||||
|
||||
```powershell
|
||||
# monitor_service.ps1
|
||||
|
||||
$serviceName = "GoTunnelServer"
|
||||
$service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
|
||||
|
||||
if ($null -eq $service) {
|
||||
Write-Host "❌ 服务未安装: $serviceName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($service.Status -eq 'Running') {
|
||||
Write-Host "✅ 服务运行正常: $serviceName"
|
||||
|
||||
# 测试 API 访问
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "http://localhost:8080" -TimeoutSec 5
|
||||
Write-Host "✅ API 访问正常"
|
||||
} catch {
|
||||
Write-Host "⚠️ API 访问失败: $_"
|
||||
}
|
||||
} else {
|
||||
Write-Host "❌ 服务未运行: $serviceName (状态: $($service.Status))"
|
||||
|
||||
# 尝试启动服务
|
||||
Write-Host "尝试启动服务..."
|
||||
Start-Service -Name $serviceName
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
$service.Refresh()
|
||||
if ($service.Status -eq 'Running') {
|
||||
Write-Host "✅ 服务启动成功"
|
||||
} else {
|
||||
Write-Host "❌ 服务启动失败"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
可以将监控脚本添加到任务计划程序,定期执行检查。
|
||||
|
||||
---
|
||||
|
||||
## 下一步
|
||||
|
||||
- 📖 查看完整文档:[SERVICE_GUIDE.md](SERVICE_GUIDE.md)
|
||||
- 🔧 配置文件说明:[README.md](README.md)
|
||||
- 🐛 问题反馈:提交 Issue
|
||||
77
README.md
77
README.md
|
|
@ -14,6 +14,8 @@
|
|||
- ✅ **连接池管理**: 高效的连接池和并发管理
|
||||
- ✅ **优雅关闭**: 支持优雅关闭和信号处理
|
||||
- ✅ **自动重连**: 客户端支持断线自动重连
|
||||
- ✅ **系统服务**: 支持注册为 Windows 服务和 Linux systemd 服务
|
||||
- ✅ **自动重启**: 服务崩溃后自动重启,保证高可用性
|
||||
- ✅ **生产级代码**: 完善的错误处理、日志记录和性能优化
|
||||
|
||||
## 系统架构
|
||||
|
|
@ -583,7 +585,80 @@ cd bin
|
|||
./client -help
|
||||
```
|
||||
|
||||
### 6. 运行测试
|
||||
### 6. 注册为系统服务(生产环境推荐)
|
||||
|
||||
#### Windows 服务
|
||||
|
||||
```powershell
|
||||
# 以管理员身份运行 PowerShell
|
||||
|
||||
# 安装 Server 服务
|
||||
.\server.exe -reg install
|
||||
|
||||
# 安装 Client 服务
|
||||
.\client.exe -server your-server.com:9000 -reg install
|
||||
|
||||
# 启动服务
|
||||
sc start GoTunnelServer
|
||||
sc start GoTunnelClient
|
||||
|
||||
# 查看服务状态
|
||||
sc query GoTunnelServer
|
||||
sc query GoTunnelClient
|
||||
|
||||
# 停止服务
|
||||
sc stop GoTunnelServer
|
||||
sc stop GoTunnelClient
|
||||
|
||||
# 卸载服务
|
||||
.\server.exe -reg uninstall
|
||||
.\client.exe -reg uninstall
|
||||
```
|
||||
|
||||
#### Linux 服务(systemd)
|
||||
|
||||
```bash
|
||||
# 以 root 身份运行
|
||||
|
||||
# 安装 Server 服务
|
||||
sudo ./server -reg install
|
||||
|
||||
# 安装 Client 服务
|
||||
sudo ./client -server your-server.com:9000 -reg install
|
||||
|
||||
# 启动服务
|
||||
sudo systemctl start GoTunnelServer
|
||||
sudo systemctl start GoTunnelClient
|
||||
|
||||
# 查看服务状态
|
||||
sudo systemctl status GoTunnelServer
|
||||
sudo systemctl status GoTunnelClient
|
||||
|
||||
# 查看日志
|
||||
sudo journalctl -u GoTunnelServer -f
|
||||
sudo journalctl -u GoTunnelClient -f
|
||||
|
||||
# 停止服务
|
||||
sudo systemctl stop GoTunnelServer
|
||||
sudo systemctl stop GoTunnelClient
|
||||
|
||||
# 卸载服务
|
||||
sudo ./server -reg uninstall
|
||||
sudo ./client -reg uninstall
|
||||
```
|
||||
|
||||
#### 服务特性
|
||||
|
||||
- ✅ **开机自启**: 系统启动时自动运行
|
||||
- ✅ **自动重启**: 服务崩溃后自动重启(Windows: 60秒后,Linux: 10秒后)
|
||||
- ✅ **优雅关闭**: 正确处理停止信号,清理资源
|
||||
- ✅ **日志管理**: Windows 使用事件查看器,Linux 使用 journalctl
|
||||
|
||||
📖 **详细文档**:
|
||||
- [服务注册完整指南](SERVICE_GUIDE.md)
|
||||
- [快速开始指南](QUICK_START_SERVICE.md)
|
||||
|
||||
### 7. 运行测试
|
||||
|
||||
```bash
|
||||
# 运行所有测试
|
||||
|
|
|
|||
|
|
@ -0,0 +1,494 @@
|
|||
# 服务注册和管理指南
|
||||
|
||||
## 概述
|
||||
|
||||
Go Tunnel 支持将 Server 和 Client 注册为系统服务,实现开机自启和自动重启功能。
|
||||
|
||||
- **Windows**: 使用 Windows Service
|
||||
- **Linux**: 使用 systemd
|
||||
|
||||
## 功能特性
|
||||
|
||||
✅ **开机自启**: 系统启动时自动运行
|
||||
✅ **自动重启**: 服务崩溃后自动重启(Windows: 60秒后重启,Linux: 10秒后重启)
|
||||
✅ **优雅关闭**: 正确处理停止信号,清理资源
|
||||
✅ **日志记录**: Windows 使用事件查看器,Linux 使用 journalctl
|
||||
✅ **管理员权限**: 自动检查权限,确保安全安装
|
||||
|
||||
## 安装要求
|
||||
|
||||
### Windows
|
||||
- 管理员权限(以管理员身份运行 PowerShell 或 CMD)
|
||||
- Windows 7 或更高版本
|
||||
|
||||
### Linux
|
||||
- root 权限(使用 sudo)
|
||||
- systemd 支持(大多数现代 Linux 发行版)
|
||||
|
||||
---
|
||||
|
||||
## Server 服务管理
|
||||
|
||||
### Windows
|
||||
|
||||
#### 1. 安装服务
|
||||
|
||||
```powershell
|
||||
# 以管理员身份运行
|
||||
.\server.exe -reg install
|
||||
|
||||
# 指定配置文件
|
||||
.\server.exe -config custom_config.yaml -reg install
|
||||
```
|
||||
|
||||
#### 2. 启动服务
|
||||
|
||||
```powershell
|
||||
# 方法 1: 使用程序命令
|
||||
.\server.exe -reg start
|
||||
|
||||
# 方法 2: 使用 sc 命令
|
||||
sc start GoTunnelServer
|
||||
|
||||
# 方法 3: 使用服务管理器(services.msc)
|
||||
```
|
||||
|
||||
#### 3. 停止服务
|
||||
|
||||
```powershell
|
||||
.\server.exe -reg stop
|
||||
# 或
|
||||
sc stop GoTunnelServer
|
||||
```
|
||||
|
||||
#### 4. 查询服务状态
|
||||
|
||||
```powershell
|
||||
.\server.exe -reg status
|
||||
# 或
|
||||
sc query GoTunnelServer
|
||||
```
|
||||
|
||||
#### 5. 卸载服务
|
||||
|
||||
```powershell
|
||||
.\server.exe -reg uninstall
|
||||
```
|
||||
|
||||
#### 6. 查看日志
|
||||
|
||||
打开"事件查看器" → "Windows 日志" → "应用程序",搜索 "GoTunnelServer"
|
||||
|
||||
### Linux
|
||||
|
||||
#### 1. 安装服务
|
||||
|
||||
```bash
|
||||
# 以 root 身份运行
|
||||
sudo ./server -reg install
|
||||
|
||||
# 指定配置文件
|
||||
sudo ./server -config /etc/gotunnel/config.yaml -reg install
|
||||
```
|
||||
|
||||
#### 2. 启动服务
|
||||
|
||||
```bash
|
||||
# 方法 1: 使用程序命令
|
||||
sudo ./server -reg start
|
||||
|
||||
# 方法 2: 使用 systemctl
|
||||
sudo systemctl start GoTunnelServer
|
||||
```
|
||||
|
||||
#### 3. 停止服务
|
||||
|
||||
```bash
|
||||
sudo ./server -reg stop
|
||||
# 或
|
||||
sudo systemctl stop GoTunnelServer
|
||||
```
|
||||
|
||||
#### 4. 查询服务状态
|
||||
|
||||
```bash
|
||||
sudo ./server -reg status
|
||||
# 或
|
||||
sudo systemctl status GoTunnelServer
|
||||
```
|
||||
|
||||
#### 5. 卸载服务
|
||||
|
||||
```bash
|
||||
sudo ./server -reg uninstall
|
||||
```
|
||||
|
||||
#### 6. 查看日志
|
||||
|
||||
```bash
|
||||
# 实时查看日志
|
||||
sudo journalctl -u GoTunnelServer -f
|
||||
|
||||
# 查看最近的日志
|
||||
sudo journalctl -u GoTunnelServer -n 100
|
||||
|
||||
# 查看今天的日志
|
||||
sudo journalctl -u GoTunnelServer --since today
|
||||
```
|
||||
|
||||
#### 7. 设置开机自启(安装时自动启用)
|
||||
|
||||
```bash
|
||||
sudo systemctl enable GoTunnelServer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Client 服务管理
|
||||
|
||||
### Windows
|
||||
|
||||
#### 1. 安装服务
|
||||
|
||||
```powershell
|
||||
# 以管理员身份运行
|
||||
.\client.exe -reg install
|
||||
|
||||
# 指定服务器地址
|
||||
.\client.exe -server example.com:9000 -reg install
|
||||
```
|
||||
|
||||
#### 2. 启动服务
|
||||
|
||||
```powershell
|
||||
.\client.exe -reg start
|
||||
# 或
|
||||
sc start GoTunnelClient
|
||||
```
|
||||
|
||||
#### 3. 停止服务
|
||||
|
||||
```powershell
|
||||
.\client.exe -reg stop
|
||||
# 或
|
||||
sc stop GoTunnelClient
|
||||
```
|
||||
|
||||
#### 4. 查询服务状态
|
||||
|
||||
```powershell
|
||||
.\client.exe -reg status
|
||||
# 或
|
||||
sc query GoTunnelClient
|
||||
```
|
||||
|
||||
#### 5. 卸载服务
|
||||
|
||||
```powershell
|
||||
.\client.exe -reg uninstall
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
#### 1. 安装服务
|
||||
|
||||
```bash
|
||||
sudo ./client -reg install
|
||||
|
||||
# 指定服务器地址
|
||||
sudo ./client -server example.com:9000 -reg install
|
||||
```
|
||||
|
||||
#### 2. 启动服务
|
||||
|
||||
```bash
|
||||
sudo ./client -reg start
|
||||
# 或
|
||||
sudo systemctl start GoTunnelClient
|
||||
```
|
||||
|
||||
#### 3. 停止服务
|
||||
|
||||
```bash
|
||||
sudo ./client -reg stop
|
||||
# 或
|
||||
sudo systemctl stop GoTunnelClient
|
||||
```
|
||||
|
||||
#### 4. 查询服务状态
|
||||
|
||||
```bash
|
||||
sudo ./client -reg status
|
||||
# 或
|
||||
sudo systemctl status GoTunnelClient
|
||||
```
|
||||
|
||||
#### 5. 卸载服务
|
||||
|
||||
```bash
|
||||
sudo ./client -reg uninstall
|
||||
```
|
||||
|
||||
#### 6. 查看日志
|
||||
|
||||
```bash
|
||||
# 实时查看日志
|
||||
sudo journalctl -u GoTunnelClient -f
|
||||
|
||||
# 查看最近的日志
|
||||
sudo journalctl -u GoTunnelClient -n 100
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 自动重启配置
|
||||
|
||||
### Windows
|
||||
|
||||
服务失败时的恢复策略:
|
||||
- **第一次失败**: 60秒后重启
|
||||
- **第二次失败**: 60秒后重启
|
||||
- **后续失败**: 60秒后重启
|
||||
- **重置失败计数**: 24小时
|
||||
|
||||
可以通过服务管理器(services.msc)查看和修改恢复选项。
|
||||
|
||||
### Linux
|
||||
|
||||
systemd 服务配置(自动生成):
|
||||
```ini
|
||||
[Service]
|
||||
Restart=always # 总是重启
|
||||
RestartSec=10 # 10秒后重启
|
||||
StartLimitBurst=5 # 60秒内最多重启5次
|
||||
StartLimitInterval=60 # 限制间隔
|
||||
```
|
||||
|
||||
修改重启策略:
|
||||
```bash
|
||||
# 编辑服务文件
|
||||
sudo systemctl edit --full GoTunnelServer
|
||||
|
||||
# 修改后重新加载
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl restart GoTunnelServer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 故障排查
|
||||
|
||||
### Windows
|
||||
|
||||
#### 服务无法启动
|
||||
|
||||
1. 检查事件查看器中的错误日志
|
||||
2. 确认可执行文件路径正确
|
||||
3. 验证配置文件存在且格式正确
|
||||
4. 检查端口是否被占用
|
||||
|
||||
```powershell
|
||||
# 查看详细错误
|
||||
sc query GoTunnelServer
|
||||
sc qc GoTunnelServer
|
||||
|
||||
# 手动测试运行
|
||||
.\server.exe -config config.yaml
|
||||
```
|
||||
|
||||
#### 权限问题
|
||||
|
||||
确保以管理员身份运行:
|
||||
```powershell
|
||||
# 检查是否以管理员运行
|
||||
[Security.Principal.WindowsPrincipal] $CurrentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$CurrentUser.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
#### 服务无法启动
|
||||
|
||||
```bash
|
||||
# 查看详细状态
|
||||
sudo systemctl status GoTunnelServer -l
|
||||
|
||||
# 查看启动失败日志
|
||||
sudo journalctl -u GoTunnelServer -xe
|
||||
|
||||
# 手动测试运行
|
||||
sudo ./server -config config.yaml
|
||||
```
|
||||
|
||||
#### 权限问题
|
||||
|
||||
```bash
|
||||
# 检查文件权限
|
||||
ls -l /etc/systemd/system/GoTunnelServer.service
|
||||
ls -l ./server
|
||||
|
||||
# 确保可执行
|
||||
sudo chmod +x ./server
|
||||
```
|
||||
|
||||
#### 端口占用
|
||||
|
||||
```bash
|
||||
# 检查端口使用情况
|
||||
sudo netstat -tlnp | grep :8080
|
||||
sudo lsof -i :8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 生产环境最佳实践
|
||||
|
||||
### 1. 日志管理
|
||||
|
||||
#### Windows
|
||||
- 定期清理事件日志
|
||||
- 考虑使用第三方日志聚合工具
|
||||
|
||||
#### Linux
|
||||
```bash
|
||||
# 限制日志大小(编辑 journald 配置)
|
||||
sudo vim /etc/systemd/journald.conf
|
||||
|
||||
# 设置日志保留
|
||||
SystemMaxUse=1G
|
||||
SystemMaxFileSize=100M
|
||||
MaxRetentionSec=30day
|
||||
|
||||
# 重启 journald
|
||||
sudo systemctl restart systemd-journald
|
||||
```
|
||||
|
||||
### 2. 监控和告警
|
||||
|
||||
#### Windows
|
||||
使用 PowerShell 脚本监控服务状态:
|
||||
```powershell
|
||||
$service = Get-Service GoTunnelServer
|
||||
if ($service.Status -ne 'Running') {
|
||||
# 发送告警
|
||||
Write-Host "Service is not running!"
|
||||
}
|
||||
```
|
||||
|
||||
#### Linux
|
||||
使用 systemd 或第三方监控工具:
|
||||
```bash
|
||||
# 检查服务状态
|
||||
systemctl is-active GoTunnelServer
|
||||
|
||||
# 配合监控工具(如 Prometheus + Node Exporter)
|
||||
```
|
||||
|
||||
### 3. 配置备份
|
||||
|
||||
定期备份配置文件和数据库:
|
||||
```bash
|
||||
# Linux
|
||||
sudo cp /path/to/config.yaml /backup/config.yaml.$(date +%Y%m%d)
|
||||
|
||||
# Windows
|
||||
Copy-Item config.yaml backup\config.yaml.$(Get-Date -Format 'yyyyMMdd')
|
||||
```
|
||||
|
||||
### 4. 安全建议
|
||||
|
||||
- ✅ 使用非 root/Administrator 用户运行(可选配置)
|
||||
- ✅ 限制配置文件权限(仅管理员可读写)
|
||||
- ✅ 启用防火墙规则
|
||||
- ✅ 定期更新程序版本
|
||||
- ✅ 监控异常连接和流量
|
||||
|
||||
### 5. 资源限制
|
||||
|
||||
#### Linux (systemd)
|
||||
编辑服务文件添加资源限制:
|
||||
```ini
|
||||
[Service]
|
||||
# 内存限制
|
||||
MemoryLimit=512M
|
||||
# 文件描述符限制
|
||||
LimitNOFILE=65536
|
||||
# CPU 使用率限制
|
||||
CPUQuota=200%
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见命令速查表
|
||||
|
||||
| 操作 | Windows | Linux |
|
||||
|------|---------|-------|
|
||||
| 安装服务 | `.\server.exe -reg install` | `sudo ./server -reg install` |
|
||||
| 启动服务 | `sc start GoTunnelServer` | `sudo systemctl start GoTunnelServer` |
|
||||
| 停止服务 | `sc stop GoTunnelServer` | `sudo systemctl stop GoTunnelServer` |
|
||||
| 重启服务 | `sc stop ... && sc start ...` | `sudo systemctl restart GoTunnelServer` |
|
||||
| 查看状态 | `sc query GoTunnelServer` | `sudo systemctl status GoTunnelServer` |
|
||||
| 查看日志 | 事件查看器 | `sudo journalctl -u GoTunnelServer -f` |
|
||||
| 卸载服务 | `.\server.exe -reg uninstall` | `sudo ./server -reg uninstall` |
|
||||
| 开机自启 | 自动启用 | `sudo systemctl enable GoTunnelServer` |
|
||||
|
||||
---
|
||||
|
||||
## 技术细节
|
||||
|
||||
### 自动重启机制
|
||||
|
||||
#### Windows
|
||||
- 使用 Windows Service Recovery Options
|
||||
- 服务崩溃时由 SCM(服务控制管理器)自动重启
|
||||
- 支持配置延迟和重试次数
|
||||
|
||||
#### Linux
|
||||
- 使用 systemd 的 `Restart=always` 配置
|
||||
- 支持 `StartLimitBurst` 和 `StartLimitInterval` 防止重启循环
|
||||
- 系统级别的进程管理和监控
|
||||
|
||||
### 优雅关闭
|
||||
|
||||
两个平台都实现了优雅关闭:
|
||||
1. 接收停止信号(SIGTERM/SIGINT)
|
||||
2. 停止接受新连接
|
||||
3. 等待现有连接完成
|
||||
4. 清理资源(关闭数据库、停止转发器)
|
||||
5. 退出程序
|
||||
|
||||
---
|
||||
|
||||
## 更新说明
|
||||
|
||||
更新服务时的步骤:
|
||||
|
||||
### Windows
|
||||
```powershell
|
||||
# 1. 停止服务
|
||||
sc stop GoTunnelServer
|
||||
|
||||
# 2. 替换可执行文件
|
||||
Copy-Item new-server.exe server.exe -Force
|
||||
|
||||
# 3. 启动服务
|
||||
sc start GoTunnelServer
|
||||
```
|
||||
|
||||
### Linux
|
||||
```bash
|
||||
# 1. 停止服务
|
||||
sudo systemctl stop GoTunnelServer
|
||||
|
||||
# 2. 替换可执行文件
|
||||
sudo cp new-server /usr/local/bin/server
|
||||
|
||||
# 3. 启动服务
|
||||
sudo systemctl start GoTunnelServer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 支持和反馈
|
||||
|
||||
如有问题或建议,请提交 Issue 或 Pull Request。
|
||||
|
|
@ -2,67 +2,183 @@ package main
|
|||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"port-forward/client/tunnel"
|
||||
"port-forward/common/service"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// clientService 客户端服务实例
|
||||
type clientService struct {
|
||||
serverAddr string
|
||||
client *tunnel.Client
|
||||
sigChan chan os.Signal
|
||||
}
|
||||
|
||||
func (s *clientService) Start() error {
|
||||
log.Println("客户端服务启动中...")
|
||||
|
||||
// 创建隧道客户端
|
||||
log.Printf("隧道客户端启动...")
|
||||
log.Printf("服务器地址: %s", s.serverAddr)
|
||||
|
||||
s.client = tunnel.NewClient(s.serverAddr)
|
||||
|
||||
// 启动客户端
|
||||
if err := s.client.Start(); err != nil {
|
||||
return fmt.Errorf("启动隧道客户端失败: %v", err)
|
||||
}
|
||||
|
||||
log.Println("===========================================")
|
||||
log.Println("隧道客户端运行中...")
|
||||
log.Println("===========================================")
|
||||
|
||||
// 等待中断信号
|
||||
s.sigChan = make(chan os.Signal, 1)
|
||||
signal.Notify(s.sigChan, os.Interrupt, syscall.SIGTERM)
|
||||
<-s.sigChan
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *clientService) Stop() error {
|
||||
log.Println("接收到关闭信号,正在关闭...")
|
||||
|
||||
// 停止客户端
|
||||
if s.client != nil {
|
||||
if err := s.client.Stop(); err != nil {
|
||||
log.Printf("停止客户端失败: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("客户端已关闭")
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 解析命令行参数
|
||||
serverAddr := flag.String("server", "localhost:9000", "隧道服务器地址 (host:port)")
|
||||
regAction := flag.String("reg", "", "服务管理: install(安装), uninstall(卸载), start(启动), stop(停止), status(状态)")
|
||||
flag.Parse()
|
||||
|
||||
// 设置日志格式
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
|
||||
// 创建隧道客户端
|
||||
log.Printf("隧道客户端启动...")
|
||||
log.Printf("服务器地址: %s", *serverAddr)
|
||||
|
||||
client := tunnel.NewClient(*serverAddr)
|
||||
|
||||
// 启动客户端
|
||||
if err := client.Start(); err != nil {
|
||||
log.Fatalf("启动隧道客户端失败: %v", err)
|
||||
// 处理服务注册相关操作
|
||||
if *regAction != "" {
|
||||
handleServiceAction(*regAction, *serverAddr)
|
||||
return
|
||||
}
|
||||
|
||||
// // 启动 pprof 调试服务器(用于性能分析和调试)
|
||||
// pprofPort := 6061
|
||||
// go func() {
|
||||
// log.Printf("启动 pprof 调试服务器: http://localhost:%d/debug/pprof/", pprofPort)
|
||||
// if err := http.ListenAndServe(":6061", nil); err != nil {
|
||||
// log.Printf("pprof 服务器启动失败: %v", err)
|
||||
// }
|
||||
// }()
|
||||
|
||||
// // 启动 goroutine 监控
|
||||
// go func() {
|
||||
// ticker := time.NewTicker(10 * time.Second)
|
||||
// defer ticker.Stop()
|
||||
// for range ticker.C {
|
||||
// numGoroutines := runtime.NumGoroutine()
|
||||
// log.Printf("[监控] 当前 Goroutine 数量: %d", numGoroutines)
|
||||
// }
|
||||
// }()
|
||||
|
||||
log.Println("===========================================")
|
||||
log.Println("隧道客户端运行中...")
|
||||
// log.Printf("调试接口: http://localhost:%d/debug/pprof/", pprofPort)
|
||||
log.Println("按 Ctrl+C 退出")
|
||||
log.Println("===========================================")
|
||||
|
||||
// 等待中断信号
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
<-sigChan
|
||||
log.Println("\n接收到关闭信号,正在关闭...")
|
||||
|
||||
// 停止客户端
|
||||
if err := client.Stop(); err != nil {
|
||||
log.Printf("停止客户端失败: %v", err)
|
||||
// 检查是否以服务模式运行(编译时根据平台自动选择实现)
|
||||
if service.IsService() {
|
||||
// 以服务模式运行
|
||||
runAsService(*serverAddr)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("客户端已关闭")
|
||||
}
|
||||
// 正常运行模式
|
||||
normalRun(*serverAddr)
|
||||
}
|
||||
|
||||
func normalRun(serverAddr string) {
|
||||
svc := &clientService{
|
||||
serverAddr: serverAddr,
|
||||
}
|
||||
|
||||
if err := svc.Start(); err != nil {
|
||||
log.Fatalf("客户端启动失败: %v", err)
|
||||
}
|
||||
|
||||
svc.Stop()
|
||||
}
|
||||
|
||||
func runAsService(serverAddr string) {
|
||||
svc := &clientService{
|
||||
serverAddr: serverAddr,
|
||||
}
|
||||
|
||||
// 创建服务包装器
|
||||
wrapper := service.NewWindowsServiceWrapper(svc)
|
||||
|
||||
// 运行服务
|
||||
err := service.RunAsService("GoTunnelClient", wrapper)
|
||||
if err != nil {
|
||||
log.Fatalf("运行服务失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func handleServiceAction(action, serverAddr string) {
|
||||
// 检查管理员权限
|
||||
if !service.IsAdmin() {
|
||||
log.Fatal("需要管理员/root权限来执行此操作")
|
||||
}
|
||||
|
||||
// 获取可执行文件路径
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
log.Fatalf("获取可执行文件路径失败: %v", err)
|
||||
}
|
||||
exePath, err = filepath.Abs(exePath)
|
||||
if err != nil {
|
||||
log.Fatalf("获取绝对路径失败: %v", err)
|
||||
}
|
||||
|
||||
// 准备服务配置
|
||||
cfg := service.ServiceConfig{
|
||||
Name: "GoTunnelClient",
|
||||
DisplayName: "Go Tunnel Client",
|
||||
Description: "Go Tunnel 隧道客户端 - 连接到隧道服务器并提供内网穿透服务",
|
||||
ExecPath: exePath,
|
||||
Args: buildServiceArgs(serverAddr),
|
||||
WorkingDir: filepath.Dir(exePath),
|
||||
}
|
||||
|
||||
// 创建服务实例
|
||||
svcInstance, err := service.NewService(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("创建服务实例失败: %v", err)
|
||||
}
|
||||
|
||||
// 执行操作
|
||||
switch strings.ToLower(action) {
|
||||
case "install":
|
||||
err = svcInstance.Install()
|
||||
case "uninstall":
|
||||
err = svcInstance.Uninstall()
|
||||
case "start":
|
||||
err = svcInstance.Start()
|
||||
case "stop":
|
||||
err = svcInstance.Stop()
|
||||
case "status":
|
||||
status, err := svcInstance.Status()
|
||||
if err != nil {
|
||||
log.Fatalf("查询服务状态失败: %v", err)
|
||||
}
|
||||
fmt.Printf("服务状态: %s\n", status)
|
||||
return
|
||||
default:
|
||||
log.Fatalf("未知操作: %s。支持的操作: install, uninstall, start, stop, status", action)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("执行操作 %s 失败: %v", action, err)
|
||||
}
|
||||
}
|
||||
|
||||
func buildServiceArgs(serverAddr string) []string {
|
||||
args := []string{}
|
||||
|
||||
// 只添加非默认的服务器地址
|
||||
if serverAddr != "" && serverAddr != "localhost:9000" {
|
||||
args = append(args, "-server", serverAddr)
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// ServiceConfig 服务配置
|
||||
type ServiceConfig struct {
|
||||
Name string // 服务名称
|
||||
DisplayName string // 服务显示名称
|
||||
Description string // 服务描述
|
||||
ExecPath string // 可执行文件路径
|
||||
Args []string // 启动参数
|
||||
WorkingDir string // 工作目录
|
||||
}
|
||||
|
||||
// Service 服务接口
|
||||
type Service interface {
|
||||
// Install 安装服务
|
||||
Install() error
|
||||
// Uninstall 卸载服务
|
||||
Uninstall() error
|
||||
// Start 启动服务
|
||||
Start() error
|
||||
// Stop 停止服务
|
||||
Stop() error
|
||||
// Status 查询服务状态
|
||||
Status() (string, error)
|
||||
}
|
||||
|
||||
// NewService 创建服务实例
|
||||
func NewService(config ServiceConfig) (Service, error) {
|
||||
// 如果没有指定可执行文件路径,使用当前程序路径
|
||||
if config.ExecPath == "" {
|
||||
execPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取可执行文件路径失败: %v", err)
|
||||
}
|
||||
config.ExecPath, err = filepath.Abs(execPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取绝对路径失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有指定工作目录,使用可执行文件所在目录
|
||||
if config.WorkingDir == "" {
|
||||
config.WorkingDir = filepath.Dir(config.ExecPath)
|
||||
}
|
||||
|
||||
// 调用平台特定的实现(编译时自动选择)
|
||||
return newPlatformService(config)
|
||||
}
|
||||
|
||||
// newPlatformService 创建平台特定的服务实例
|
||||
// 该函数在 service_windows.go、service_linux.go、service_unsupported.go 中实现
|
||||
// 编译时会根据 build tags 自动选择对应平台的实现
|
||||
|
||||
// IsAdmin 检查当前用户是否有管理员权限
|
||||
// 该函数在 service_windows.go、service_linux.go、service_unsupported.go 中实现
|
||||
// 编译时会根据 build tags 自动选择对应平台的实现
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type linuxService struct {
|
||||
config ServiceConfig
|
||||
}
|
||||
|
||||
// newPlatformService 创建 Linux 服务实例
|
||||
func newPlatformService(config ServiceConfig) (Service, error) {
|
||||
return &linuxService{config: config}, nil
|
||||
}
|
||||
|
||||
// IsAdmin 检查当前用户是否有管理员权限(Linux)
|
||||
func IsAdmin() bool {
|
||||
return os.Geteuid() == 0
|
||||
}
|
||||
|
||||
// IsService 检查是否以服务模式运行(Linux 上始终返回 false)
|
||||
// Linux 的 systemd 服务不需要特殊的服务模式,直接以普通模式运行即可
|
||||
func IsService() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// WindowsServiceHandler 在 Linux 上的占位接口
|
||||
type WindowsServiceHandler interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
}
|
||||
|
||||
// NewWindowsServiceWrapper 在 Linux 上不可用
|
||||
func NewWindowsServiceWrapper(handler WindowsServiceHandler) interface{} {
|
||||
panic("NewWindowsServiceWrapper is not available on Linux")
|
||||
}
|
||||
|
||||
// RunAsService 在 Linux 上不可用
|
||||
func RunAsService(name string, handler interface{}) error {
|
||||
return fmt.Errorf("RunAsService is not available on Linux")
|
||||
}
|
||||
|
||||
func (s *linuxService) Install() error {
|
||||
// 生成 systemd service 文件
|
||||
serviceContent, err := s.generateServiceFile()
|
||||
if err != nil {
|
||||
return fmt.Errorf("生成服务配置文件失败: %v", err)
|
||||
}
|
||||
|
||||
// 服务文件路径
|
||||
serviceFilePath := fmt.Sprintf("/etc/systemd/system/%s.service", s.config.Name)
|
||||
|
||||
// 写入服务文件
|
||||
err = os.WriteFile(serviceFilePath, []byte(serviceContent), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("写入服务配置文件失败: %v", err)
|
||||
}
|
||||
|
||||
// 重新加载 systemd
|
||||
cmd := exec.Command("systemctl", "daemon-reload")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("重新加载 systemd 失败: %v", err)
|
||||
}
|
||||
|
||||
// 启用服务(开机自启)
|
||||
cmd = exec.Command("systemctl", "enable", s.config.Name)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("启用服务失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("服务 %s 安装成功\n", s.config.DisplayName)
|
||||
fmt.Printf("服务文件: %s\n", serviceFilePath)
|
||||
fmt.Printf("可执行文件: %s\n", s.config.ExecPath)
|
||||
fmt.Printf("启动参数: %s\n", strings.Join(s.config.Args, " "))
|
||||
fmt.Printf("工作目录: %s\n", s.config.WorkingDir)
|
||||
fmt.Println("\n使用以下命令管理服务:")
|
||||
fmt.Printf(" 启动服务: sudo systemctl start %s\n", s.config.Name)
|
||||
fmt.Printf(" 停止服务: sudo systemctl stop %s\n", s.config.Name)
|
||||
fmt.Printf(" 查看状态: sudo systemctl status %s\n", s.config.Name)
|
||||
fmt.Printf(" 查看日志: sudo journalctl -u %s -f\n", s.config.Name)
|
||||
fmt.Printf(" 卸载服务: sudo %s -reg uninstall\n", s.config.ExecPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *linuxService) Uninstall() error {
|
||||
// 停止服务
|
||||
cmd := exec.Command("systemctl", "stop", s.config.Name)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Printf("警告: 停止服务失败: %v\n", err)
|
||||
}
|
||||
|
||||
// 禁用服务
|
||||
cmd = exec.Command("systemctl", "disable", s.config.Name)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Printf("警告: 禁用服务失败: %v\n", err)
|
||||
}
|
||||
|
||||
// 删除服务文件
|
||||
serviceFilePath := fmt.Sprintf("/etc/systemd/system/%s.service", s.config.Name)
|
||||
err := os.Remove(serviceFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("删除服务配置文件失败: %v", err)
|
||||
}
|
||||
|
||||
// 重新加载 systemd
|
||||
cmd = exec.Command("systemctl", "daemon-reload")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("重新加载 systemd 失败: %v", err)
|
||||
}
|
||||
|
||||
// 重置失败状态
|
||||
cmd = exec.Command("systemctl", "reset-failed")
|
||||
cmd.Run() // 忽略错误
|
||||
|
||||
fmt.Printf("服务 %s 卸载成功\n", s.config.DisplayName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *linuxService) Start() error {
|
||||
cmd := exec.Command("systemctl", "start", s.config.Name)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("启动服务失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("服务 %s 启动成功\n", s.config.DisplayName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *linuxService) Stop() error {
|
||||
cmd := exec.Command("systemctl", "stop", s.config.Name)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("停止服务失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("服务 %s 停止成功\n", s.config.DisplayName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *linuxService) Status() (string, error) {
|
||||
cmd := exec.Command("systemctl", "is-active", s.config.Name)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
// 即使服务不活跃也会返回错误,所以检查输出
|
||||
status := strings.TrimSpace(string(output))
|
||||
if status != "" {
|
||||
return status, nil
|
||||
}
|
||||
return "", fmt.Errorf("查询服务状态失败: %v", err)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
}
|
||||
|
||||
func (s *linuxService) generateServiceFile() (string, error) {
|
||||
// systemd service 文件模板
|
||||
tmpl := `[Unit]
|
||||
Description={{.Description}}
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory={{.WorkingDir}}
|
||||
ExecStart={{.ExecStart}}
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
# 安全设置
|
||||
LimitNOFILE=65536
|
||||
LimitNPROC=65536
|
||||
|
||||
# 性能和资源限制
|
||||
TimeoutStartSec=0
|
||||
TimeoutStopSec=30
|
||||
|
||||
# 失败处理
|
||||
StartLimitInterval=60
|
||||
StartLimitBurst=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
`
|
||||
|
||||
// 构建完整的启动命令
|
||||
execStart := s.config.ExecPath
|
||||
if len(s.config.Args) > 0 {
|
||||
execStart += " " + strings.Join(s.config.Args, " ")
|
||||
}
|
||||
|
||||
// 准备模板数据
|
||||
data := map[string]string{
|
||||
"Description": s.config.Description,
|
||||
"WorkingDir": s.config.WorkingDir,
|
||||
"ExecStart": execStart,
|
||||
}
|
||||
|
||||
// 解析并执行模板
|
||||
t, err := template.New("service").Parse(tmpl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var buf strings.Builder
|
||||
err = t.Execute(&buf, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
//go:build !windows && !linux
|
||||
// +build !windows,!linux
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type unsupportedService struct {
|
||||
config ServiceConfig
|
||||
}
|
||||
|
||||
// newPlatformService 不支持的平台
|
||||
func newPlatformService(config ServiceConfig) (Service, error) {
|
||||
return nil, fmt.Errorf("不支持的操作系统: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
// IsAdmin 不支持的平台
|
||||
func IsAdmin() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsService 检查是否以服务模式运行(不支持的平台上始终返回 false)
|
||||
func IsService() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// WindowsServiceHandler 在不支持的平台上的占位接口
|
||||
type WindowsServiceHandler interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
}
|
||||
|
||||
// NewWindowsServiceWrapper 在不支持的平台上不可用
|
||||
func NewWindowsServiceWrapper(handler WindowsServiceHandler) interface{} {
|
||||
panic("NewWindowsServiceWrapper is not available on this platform")
|
||||
}
|
||||
|
||||
// RunAsService 在不支持的平台上不可用
|
||||
func RunAsService(name string, handler interface{}) error {
|
||||
return fmt.Errorf("RunAsService is not available on this platform")
|
||||
}
|
||||
|
|
@ -0,0 +1,275 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/svc"
|
||||
"golang.org/x/sys/windows/svc/mgr"
|
||||
)
|
||||
|
||||
type windowsService struct {
|
||||
config ServiceConfig
|
||||
}
|
||||
|
||||
// newPlatformService 创建 Windows 服务实例
|
||||
func newPlatformService(config ServiceConfig) (Service, error) {
|
||||
return &windowsService{config: config}, nil
|
||||
}
|
||||
|
||||
func (s *windowsService) Install() error {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接服务管理器失败: %v", err)
|
||||
}
|
||||
defer m.Disconnect()
|
||||
|
||||
// 检查服务是否已存在
|
||||
service, err := m.OpenService(s.config.Name)
|
||||
if err == nil {
|
||||
service.Close()
|
||||
return fmt.Errorf("服务 %s 已存在", s.config.Name)
|
||||
}
|
||||
|
||||
// 构建完整的启动命令
|
||||
exePath := s.config.ExecPath
|
||||
args := s.config.Args
|
||||
|
||||
// 创建服务配置
|
||||
config := mgr.Config{
|
||||
DisplayName: s.config.DisplayName,
|
||||
Description: s.config.Description,
|
||||
StartType: mgr.StartAutomatic, // 自动启动
|
||||
ServiceStartName: "", // 使用 LocalSystem 账户
|
||||
// 依赖项(可选)
|
||||
Dependencies: []string{},
|
||||
}
|
||||
|
||||
// 创建服务
|
||||
service, err = m.CreateService(s.config.Name, exePath, config, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建服务失败: %v", err)
|
||||
}
|
||||
defer service.Close()
|
||||
|
||||
// 设置服务恢复选项(失败后自动重启)
|
||||
err = setRecoveryOptions(service)
|
||||
if err != nil {
|
||||
// 不阻止安装,仅记录警告
|
||||
fmt.Printf("警告: 设置服务恢复选项失败: %v\n", err)
|
||||
}
|
||||
|
||||
fmt.Printf("服务 %s 安装成功\n", s.config.DisplayName)
|
||||
fmt.Printf("可执行文件: %s\n", exePath)
|
||||
fmt.Printf("启动参数: %s\n", strings.Join(args, " "))
|
||||
fmt.Printf("工作目录: %s\n", s.config.WorkingDir)
|
||||
fmt.Println("\n使用以下命令管理服务:")
|
||||
fmt.Printf(" 启动服务: sc start %s\n", s.config.Name)
|
||||
fmt.Printf(" 停止服务: sc stop %s\n", s.config.Name)
|
||||
fmt.Printf(" 查询状态: sc query %s\n", s.config.Name)
|
||||
fmt.Printf(" 卸载服务: %s -reg uninstall\n", exePath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *windowsService) Uninstall() error {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接服务管理器失败: %v", err)
|
||||
}
|
||||
defer m.Disconnect()
|
||||
|
||||
service, err := m.OpenService(s.config.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("打开服务失败: %v", err)
|
||||
}
|
||||
defer service.Close()
|
||||
|
||||
// 先停止服务
|
||||
status, err := service.Query()
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询服务状态失败: %v", err)
|
||||
}
|
||||
|
||||
if status.State != svc.Stopped {
|
||||
fmt.Println("正在停止服务...")
|
||||
_, err = service.Control(svc.Stop)
|
||||
if err != nil {
|
||||
fmt.Printf("警告: 停止服务失败: %v\n", err)
|
||||
} else {
|
||||
// 等待服务停止
|
||||
for i := 0; i < 30; i++ {
|
||||
status, err = service.Query()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if status.State == svc.Stopped {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除服务
|
||||
err = service.Delete()
|
||||
if err != nil {
|
||||
return fmt.Errorf("删除服务失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("服务 %s 卸载成功\n", s.config.DisplayName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *windowsService) Start() error {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接服务管理器失败: %v", err)
|
||||
}
|
||||
defer m.Disconnect()
|
||||
|
||||
service, err := m.OpenService(s.config.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("打开服务失败: %v", err)
|
||||
}
|
||||
defer service.Close()
|
||||
|
||||
err = service.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("启动服务失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("服务 %s 启动成功\n", s.config.DisplayName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *windowsService) Stop() error {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接服务管理器失败: %v", err)
|
||||
}
|
||||
defer m.Disconnect()
|
||||
|
||||
service, err := m.OpenService(s.config.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("打开服务失败: %v", err)
|
||||
}
|
||||
defer service.Close()
|
||||
|
||||
_, err = service.Control(svc.Stop)
|
||||
if err != nil {
|
||||
return fmt.Errorf("停止服务失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("服务 %s 停止成功\n", s.config.DisplayName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *windowsService) Status() (string, error) {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("连接服务管理器失败: %v", err)
|
||||
}
|
||||
defer m.Disconnect()
|
||||
|
||||
service, err := m.OpenService(s.config.Name)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("打开服务失败: %v", err)
|
||||
}
|
||||
defer service.Close()
|
||||
|
||||
status, err := service.Query()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("查询服务状态失败: %v", err)
|
||||
}
|
||||
|
||||
return stateToString(status.State), nil
|
||||
}
|
||||
|
||||
func stateToString(state svc.State) string {
|
||||
switch state {
|
||||
case svc.Stopped:
|
||||
return "已停止"
|
||||
case svc.StartPending:
|
||||
return "正在启动"
|
||||
case svc.StopPending:
|
||||
return "正在停止"
|
||||
case svc.Running:
|
||||
return "运行中"
|
||||
case svc.ContinuePending:
|
||||
return "继续挂起"
|
||||
case svc.PausePending:
|
||||
return "暂停挂起"
|
||||
case svc.Paused:
|
||||
return "已暂停"
|
||||
default:
|
||||
return "未知"
|
||||
}
|
||||
}
|
||||
|
||||
// setRecoveryOptions 设置服务失败恢复选项
|
||||
func setRecoveryOptions(service *mgr.Service) error {
|
||||
// 第一次失败:1分钟后重启
|
||||
// 第二次失败:1分钟后重启
|
||||
// 后续失败:1分钟后重启
|
||||
// 重置失败计数:24小时
|
||||
actions := []mgr.RecoveryAction{
|
||||
{Type: mgr.ServiceRestart, Delay: 60 * time.Second},
|
||||
{Type: mgr.ServiceRestart, Delay: 60 * time.Second},
|
||||
{Type: mgr.ServiceRestart, Delay: 60 * time.Second},
|
||||
}
|
||||
|
||||
return service.SetRecoveryActions(actions, 24*60*60) // 24小时重置
|
||||
}
|
||||
|
||||
// IsAdmin 检查是否具有管理员权限(Windows)
|
||||
func IsAdmin() bool {
|
||||
var sid *windows.SID
|
||||
err := windows.AllocateAndInitializeSid(
|
||||
&windows.SECURITY_NT_AUTHORITY,
|
||||
2,
|
||||
windows.SECURITY_BUILTIN_DOMAIN_RID,
|
||||
windows.DOMAIN_ALIAS_RID_ADMINS,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
&sid)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer windows.FreeSid(sid)
|
||||
|
||||
token := windows.Token(0)
|
||||
member, err := token.IsMember(sid)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return member
|
||||
}
|
||||
|
||||
// RunAsService 以服务模式运行
|
||||
func RunAsService(name string, handler svc.Handler) error {
|
||||
isService, err := svc.IsWindowsService()
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查服务模式失败: %v", err)
|
||||
}
|
||||
|
||||
if !isService {
|
||||
return fmt.Errorf("不是以服务模式运行")
|
||||
}
|
||||
|
||||
return svc.Run(name, handler)
|
||||
}
|
||||
|
||||
// IsService 检查是否以服务模式运行(Windows)
|
||||
func IsService() bool {
|
||||
isService, err := svc.IsWindowsService()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return isService
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"golang.org/x/sys/windows/svc"
|
||||
)
|
||||
|
||||
// WindowsServiceHandler Windows 服务处理器接口
|
||||
type WindowsServiceHandler interface {
|
||||
// Start 启动服务逻辑
|
||||
Start() error
|
||||
// Stop 停止服务逻辑
|
||||
Stop() error
|
||||
}
|
||||
|
||||
// windowsServiceWrapper Windows 服务包装器
|
||||
type windowsServiceWrapper struct {
|
||||
handler WindowsServiceHandler
|
||||
}
|
||||
|
||||
// NewWindowsServiceWrapper 创建 Windows 服务包装器
|
||||
func NewWindowsServiceWrapper(handler WindowsServiceHandler) svc.Handler {
|
||||
return &windowsServiceWrapper{handler: handler}
|
||||
}
|
||||
|
||||
// Execute 实现 svc.Handler 接口
|
||||
func (w *windowsServiceWrapper) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
|
||||
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
|
||||
|
||||
// 通知服务控制管理器服务正在启动
|
||||
changes <- svc.Status{State: svc.StartPending}
|
||||
|
||||
// 启动服务
|
||||
err := w.handler.Start()
|
||||
if err != nil {
|
||||
log.Printf("启动服务失败: %v", err)
|
||||
return true, 1
|
||||
}
|
||||
|
||||
// 通知服务控制管理器服务已启动
|
||||
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
|
||||
|
||||
log.Println("服务已启动,等待控制命令...")
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case c := <-r:
|
||||
switch c.Cmd {
|
||||
case svc.Interrogate:
|
||||
// 服务状态查询
|
||||
changes <- c.CurrentStatus
|
||||
case svc.Stop, svc.Shutdown:
|
||||
// 收到停止命令
|
||||
log.Println("收到停止命令,正在关闭服务...")
|
||||
changes <- svc.Status{State: svc.StopPending}
|
||||
|
||||
// 停止服务
|
||||
err := w.handler.Stop()
|
||||
if err != nil {
|
||||
log.Printf("停止服务失败: %v", err)
|
||||
}
|
||||
|
||||
// 通知服务已停止
|
||||
break loop
|
||||
default:
|
||||
log.Printf("收到未知控制命令: %v", c.Cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, 0
|
||||
}
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
module port-forward
|
||||
|
||||
go 1.21
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.4
|
||||
|
||||
require (
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require golang.org/x/sys v0.37.0 // indirect
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
|
|
|||
|
|
@ -2,86 +2,91 @@ package main
|
|||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"port-forward/common/service"
|
||||
"port-forward/server/api"
|
||||
"port-forward/server/config"
|
||||
"port-forward/server/db"
|
||||
"port-forward/server/forwarder"
|
||||
"port-forward/server/tunnel"
|
||||
"port-forward/server/utils"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 解析命令行参数
|
||||
configPath := flag.String("config", "config.yaml", "配置文件路径")
|
||||
flag.Parse()
|
||||
// serverService 服务实例
|
||||
type serverService struct {
|
||||
configPath string
|
||||
cfg *config.Config
|
||||
database *db.Database
|
||||
fwdManager *forwarder.Manager
|
||||
tunnelServer *tunnel.Server
|
||||
apiHandler *api.Handler
|
||||
sigChan chan os.Signal
|
||||
}
|
||||
|
||||
func (s *serverService) Start() error {
|
||||
log.Println("服务启动中...")
|
||||
|
||||
// 加载配置
|
||||
log.Println("加载配置文件...")
|
||||
cfg, err := config.Load(*configPath)
|
||||
cfg, err := config.Load(s.configPath)
|
||||
if err != nil {
|
||||
log.Fatalf("加载配置失败: %v", err)
|
||||
return fmt.Errorf("加载配置失败: %v", err)
|
||||
}
|
||||
s.cfg = cfg
|
||||
|
||||
// 初始化数据库
|
||||
log.Println("初始化数据库...")
|
||||
database, err := db.New(cfg.Database.Path)
|
||||
if err != nil {
|
||||
log.Fatalf("初始化数据库失败: %v", err)
|
||||
return fmt.Errorf("初始化数据库失败: %v", err)
|
||||
}
|
||||
defer database.Close()
|
||||
s.database = database
|
||||
|
||||
// 创建转发器管理器
|
||||
log.Println("创建转发器管理器...")
|
||||
fwdManager := forwarder.NewManager()
|
||||
s.fwdManager = forwarder.NewManager()
|
||||
|
||||
// 如果启用隧道,启动隧道服务器
|
||||
var tunnelServer *tunnel.Server
|
||||
if cfg.Tunnel.Enabled {
|
||||
log.Println("启动隧道服务器...")
|
||||
tunnelServer = tunnel.NewServer(cfg.Tunnel.ListenPort)
|
||||
if err := tunnelServer.Start(); err != nil {
|
||||
log.Fatalf("启动隧道服务器失败: %v", err)
|
||||
s.tunnelServer = tunnel.NewServer(cfg.Tunnel.ListenPort)
|
||||
if err := s.tunnelServer.Start(); err != nil {
|
||||
return fmt.Errorf("启动隧道服务器失败: %v", err)
|
||||
}
|
||||
defer tunnelServer.Stop()
|
||||
}
|
||||
|
||||
// 从数据库加载现有映射并启动转发器
|
||||
log.Println("加载现有端口映射...")
|
||||
mappings, err := database.GetAllMappings()
|
||||
if err != nil {
|
||||
log.Fatalf("加载端口映射失败: %v", err)
|
||||
return fmt.Errorf("加载端口映射失败: %v", err)
|
||||
}
|
||||
|
||||
for _, mapping := range mappings {
|
||||
// 验证端口在范围内
|
||||
// if mapping.SourcePort < cfg.PortRange.From || mapping.SourcePort > cfg.PortRange.End {
|
||||
// log.Printf("警告: 端口 %d 超出范围,跳过", mapping.SourcePort)
|
||||
// continue
|
||||
// }
|
||||
|
||||
used := utils.PortCheck(mapping.SourcePort)
|
||||
|
||||
if used {
|
||||
log.Fatalf("警告: 端口 %d 已被占! 将退出程序", mapping.SourcePort)
|
||||
return fmt.Errorf("端口 %d 已被占用", mapping.SourcePort)
|
||||
}
|
||||
|
||||
var err error
|
||||
if mapping.UseTunnel {
|
||||
// 隧道模式:检查隧道服务器是否可用
|
||||
if !cfg.Tunnel.Enabled || tunnelServer == nil {
|
||||
if !cfg.Tunnel.Enabled || s.tunnelServer == nil {
|
||||
log.Printf("警告: 端口 %d 需要隧道模式但隧道服务未启用,跳过", mapping.SourcePort)
|
||||
continue
|
||||
}
|
||||
err = fwdManager.AddTunnel(mapping.SourcePort, mapping.TargetHost, mapping.TargetPort, tunnelServer)
|
||||
err = s.fwdManager.AddTunnel(mapping.SourcePort, mapping.TargetHost, mapping.TargetPort, s.tunnelServer)
|
||||
} else {
|
||||
// 直接模式
|
||||
err = fwdManager.Add(mapping.SourcePort, mapping.TargetHost, mapping.TargetPort)
|
||||
err = s.fwdManager.Add(mapping.SourcePort, mapping.TargetHost, mapping.TargetPort)
|
||||
}
|
||||
|
||||
|
||||
if err != nil {
|
||||
log.Printf("警告: 启动端口 %d 的转发失败: %v", mapping.SourcePort, err)
|
||||
}
|
||||
|
|
@ -91,61 +96,174 @@ func main() {
|
|||
|
||||
// 创建 HTTP API 处理器
|
||||
log.Println("初始化 HTTP API...")
|
||||
apiHandler := api.NewHandler(
|
||||
database,
|
||||
fwdManager,
|
||||
tunnelServer,
|
||||
// cfg.PortRange.From,
|
||||
// cfg.PortRange.End,
|
||||
)
|
||||
s.apiHandler = api.NewHandler(database, s.fwdManager, s.tunnelServer)
|
||||
|
||||
// 启动 HTTP API 服务器
|
||||
go func() {
|
||||
if err := api.Start(apiHandler, cfg.API.ListenPort); err != nil {
|
||||
if err := api.Start(s.apiHandler, cfg.API.ListenPort); err != nil {
|
||||
log.Fatalf("启动 HTTP API 服务失败: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// // 启动 pprof 调试服务器(用于性能分析和调试)
|
||||
// pprofPort := 6060
|
||||
// go func() {
|
||||
// log.Printf("启动 pprof 调试服务器: http://localhost:%d/debug/pprof/", pprofPort)
|
||||
// if err := http.ListenAndServe(":6060", nil); err != nil {
|
||||
// log.Printf("pprof 服务器启动失败: %v", err)
|
||||
// }
|
||||
// }()
|
||||
|
||||
// // 启动 goroutine 监控
|
||||
// go func() {
|
||||
// ticker := time.NewTicker(10 * time.Second)
|
||||
// defer ticker.Stop()
|
||||
// for range ticker.C {
|
||||
// numGoroutines := runtime.NumGoroutine()
|
||||
// log.Printf("[监控] 当前 Goroutine 数量: %d", numGoroutines)
|
||||
// }
|
||||
// }()
|
||||
|
||||
log.Println("===========================================")
|
||||
log.Printf("服务器启动成功!")
|
||||
// log.Printf("端口范围: %d-%d", cfg.PortRange.From, cfg.PortRange.End)
|
||||
log.Printf("HTTP API: http://localhost:%d", cfg.API.ListenPort)
|
||||
// log.Printf("调试接口: http://localhost:%d/debug/pprof/", pprofPort)
|
||||
if cfg.Tunnel.Enabled {
|
||||
log.Printf("隧道服务: 端口 %d", cfg.Tunnel.ListenPort)
|
||||
}
|
||||
log.Println("===========================================")
|
||||
|
||||
// 等待中断信号
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||
s.sigChan = make(chan os.Signal, 1)
|
||||
signal.Notify(s.sigChan, os.Interrupt, syscall.SIGTERM)
|
||||
<-s.sigChan
|
||||
|
||||
<-sigChan
|
||||
log.Println("\n接收到关闭信号,正在优雅关闭...")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *serverService) Stop() error {
|
||||
log.Println("接收到关闭信号,正在优雅关闭...")
|
||||
|
||||
// 创建关闭上下文
|
||||
// 停止所有转发器
|
||||
log.Println("停止所有端口转发...")
|
||||
fwdManager.StopAll()
|
||||
if s.fwdManager != nil {
|
||||
log.Println("停止所有端口转发...")
|
||||
s.fwdManager.StopAll()
|
||||
}
|
||||
|
||||
// 停止隧道服务器
|
||||
if s.tunnelServer != nil {
|
||||
log.Println("停止隧道服务器...")
|
||||
s.tunnelServer.Stop()
|
||||
}
|
||||
|
||||
// 关闭数据库
|
||||
if s.database != nil {
|
||||
log.Println("关闭数据库...")
|
||||
s.database.Close()
|
||||
}
|
||||
|
||||
log.Println("服务器已关闭")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 解析命令行参数
|
||||
configPath := flag.String("config", "config.yaml", "配置文件路径")
|
||||
regAction := flag.String("reg", "", "服务管理: install(安装), uninstall(卸载), start(启动), stop(停止), status(状态)")
|
||||
flag.Parse()
|
||||
|
||||
// 设置日志格式
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
|
||||
// 处理服务注册相关操作
|
||||
if *regAction != "" {
|
||||
handleServiceAction(*regAction, *configPath)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否以服务模式运行(编译时根据平台自动选择实现)
|
||||
if service.IsService() {
|
||||
// 以服务模式运行
|
||||
runAsService(*configPath)
|
||||
return
|
||||
}
|
||||
|
||||
// 正常运行模式
|
||||
normalRun(*configPath)
|
||||
}
|
||||
|
||||
func normalRun(configPath string) {
|
||||
svc := &serverService{
|
||||
configPath: configPath,
|
||||
}
|
||||
|
||||
if err := svc.Start(); err != nil {
|
||||
log.Fatalf("服务启动失败: %v", err)
|
||||
}
|
||||
|
||||
svc.Stop()
|
||||
}
|
||||
|
||||
func runAsService(configPath string) {
|
||||
svc := &serverService{
|
||||
configPath: configPath,
|
||||
}
|
||||
|
||||
// 创建服务包装器
|
||||
wrapper := service.NewWindowsServiceWrapper(svc)
|
||||
|
||||
// 运行服务
|
||||
err := service.RunAsService("GoTunnelServer", wrapper)
|
||||
if err != nil {
|
||||
log.Fatalf("运行服务失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func handleServiceAction(action, configPath string) {
|
||||
// 检查管理员权限
|
||||
if !service.IsAdmin() {
|
||||
log.Fatal("需要管理员/root权限来执行此操作")
|
||||
}
|
||||
|
||||
// 获取可执行文件路径
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
log.Fatalf("获取可执行文件路径失败: %v", err)
|
||||
}
|
||||
exePath, err = filepath.Abs(exePath)
|
||||
if err != nil {
|
||||
log.Fatalf("获取绝对路径失败: %v", err)
|
||||
}
|
||||
|
||||
// 准备服务配置
|
||||
cfg := service.ServiceConfig{
|
||||
Name: "GoTunnelServer",
|
||||
DisplayName: "Go Tunnel Server",
|
||||
Description: "Go Tunnel 端口转发服务器 - 提供端口转发和隧道服务",
|
||||
ExecPath: exePath,
|
||||
Args: buildServiceArgs(configPath),
|
||||
WorkingDir: filepath.Dir(exePath),
|
||||
}
|
||||
|
||||
// 创建服务实例
|
||||
svcInstance, err := service.NewService(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("创建服务实例失败: %v", err)
|
||||
}
|
||||
|
||||
// 执行操作
|
||||
switch strings.ToLower(action) {
|
||||
case "install":
|
||||
err = svcInstance.Install()
|
||||
case "uninstall":
|
||||
err = svcInstance.Uninstall()
|
||||
case "start":
|
||||
err = svcInstance.Start()
|
||||
case "stop":
|
||||
err = svcInstance.Stop()
|
||||
case "status":
|
||||
status, err := svcInstance.Status()
|
||||
if err != nil {
|
||||
log.Fatalf("查询服务状态失败: %v", err)
|
||||
}
|
||||
fmt.Printf("服务状态: %s\n", status)
|
||||
return
|
||||
default:
|
||||
log.Fatalf("未知操作: %s。支持的操作: install, uninstall, start, stop, status", action)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("执行操作 %s 失败: %v", action, err)
|
||||
}
|
||||
}
|
||||
|
||||
func buildServiceArgs(configPath string) []string {
|
||||
args := []string{}
|
||||
|
||||
// 只添加非默认的配置路径
|
||||
if configPath != "" && configPath != "config.yaml" {
|
||||
args = append(args, "-config", configPath)
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue