feat: 支持了服务注册(windows与linux平台)

This commit is contained in:
pqcqaq 2025-10-18 20:25:09 +08:00
parent dcd8945c84
commit 63db36ce01
12 changed files with 2002 additions and 112 deletions

403
QUICK_START_SERVICE.md Normal file
View File

@ -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

View File

@ -14,6 +14,8 @@
- ✅ **连接池管理**: 高效的连接池和并发管理 - ✅ **连接池管理**: 高效的连接池和并发管理
- ✅ **优雅关闭**: 支持优雅关闭和信号处理 - ✅ **优雅关闭**: 支持优雅关闭和信号处理
- ✅ **自动重连**: 客户端支持断线自动重连 - ✅ **自动重连**: 客户端支持断线自动重连
- ✅ **系统服务**: 支持注册为 Windows 服务和 Linux systemd 服务
- ✅ **自动重启**: 服务崩溃后自动重启,保证高可用性
- ✅ **生产级代码**: 完善的错误处理、日志记录和性能优化 - ✅ **生产级代码**: 完善的错误处理、日志记录和性能优化
## 系统架构 ## 系统架构
@ -583,7 +585,80 @@ cd bin
./client -help ./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 ```bash
# 运行所有测试 # 运行所有测试

494
SERVICE_GUIDE.md Normal file
View File

@ -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。

View File

@ -2,67 +2,183 @@ package main
import ( import (
"flag" "flag"
"fmt"
"log" "log"
"os" "os"
"os/signal" "os/signal"
"path/filepath"
"port-forward/client/tunnel" "port-forward/client/tunnel"
"port-forward/common/service"
"strings"
"syscall" "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() { func main() {
// 解析命令行参数 // 解析命令行参数
serverAddr := flag.String("server", "localhost:9000", "隧道服务器地址 (host:port)") serverAddr := flag.String("server", "localhost:9000", "隧道服务器地址 (host:port)")
regAction := flag.String("reg", "", "服务管理: install(安装), uninstall(卸载), start(启动), stop(停止), status(状态)")
flag.Parse() flag.Parse()
// 设置日志格式
log.SetFlags(log.LstdFlags | log.Lshortfile) log.SetFlags(log.LstdFlags | log.Lshortfile)
// 创建隧道客户端 // 处理服务注册相关操作
log.Printf("隧道客户端启动...") if *regAction != "" {
log.Printf("服务器地址: %s", *serverAddr) handleServiceAction(*regAction, *serverAddr)
return
client := tunnel.NewClient(*serverAddr)
// 启动客户端
if err := client.Start(); err != nil {
log.Fatalf("启动隧道客户端失败: %v", err)
} }
// // 启动 pprof 调试服务器(用于性能分析和调试) // 检查是否以服务模式运行(编译时根据平台自动选择实现)
// pprofPort := 6061 if service.IsService() {
// go func() { // 以服务模式运行
// log.Printf("启动 pprof 调试服务器: http://localhost:%d/debug/pprof/", pprofPort) runAsService(*serverAddr)
// if err := http.ListenAndServe(":6061", nil); err != nil { return
// 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)
} }
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
}

View File

@ -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 自动选择对应平台的实现

View File

@ -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
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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
}

View File

@ -1,8 +1,12 @@
module port-forward module port-forward
go 1.21 go 1.24.0
toolchain go1.24.4
require ( require (
github.com/mattn/go-sqlite3 v1.14.22 github.com/mattn/go-sqlite3 v1.14.22
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require golang.org/x/sys v0.37.0 // indirect

View File

@ -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 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -2,86 +2,91 @@ package main
import ( import (
"flag" "flag"
"fmt"
"log" "log"
"os" "os"
"os/signal" "os/signal"
"path/filepath"
"port-forward/common/service"
"port-forward/server/api" "port-forward/server/api"
"port-forward/server/config" "port-forward/server/config"
"port-forward/server/db" "port-forward/server/db"
"port-forward/server/forwarder" "port-forward/server/forwarder"
"port-forward/server/tunnel" "port-forward/server/tunnel"
"port-forward/server/utils" "port-forward/server/utils"
"strings"
"syscall" "syscall"
) )
func main() { // serverService 服务实例
// 解析命令行参数 type serverService struct {
configPath := flag.String("config", "config.yaml", "配置文件路径") configPath string
flag.Parse() 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("加载配置文件...") log.Println("加载配置文件...")
cfg, err := config.Load(*configPath) cfg, err := config.Load(s.configPath)
if err != nil { if err != nil {
log.Fatalf("加载配置失败: %v", err) return fmt.Errorf("加载配置失败: %v", err)
} }
s.cfg = cfg
// 初始化数据库 // 初始化数据库
log.Println("初始化数据库...") log.Println("初始化数据库...")
database, err := db.New(cfg.Database.Path) database, err := db.New(cfg.Database.Path)
if err != nil { if err != nil {
log.Fatalf("初始化数据库失败: %v", err) return fmt.Errorf("初始化数据库失败: %v", err)
} }
defer database.Close() s.database = database
// 创建转发器管理器 // 创建转发器管理器
log.Println("创建转发器管理器...") log.Println("创建转发器管理器...")
fwdManager := forwarder.NewManager() s.fwdManager = forwarder.NewManager()
// 如果启用隧道,启动隧道服务器 // 如果启用隧道,启动隧道服务器
var tunnelServer *tunnel.Server
if cfg.Tunnel.Enabled { if cfg.Tunnel.Enabled {
log.Println("启动隧道服务器...") log.Println("启动隧道服务器...")
tunnelServer = tunnel.NewServer(cfg.Tunnel.ListenPort) s.tunnelServer = tunnel.NewServer(cfg.Tunnel.ListenPort)
if err := tunnelServer.Start(); err != nil { if err := s.tunnelServer.Start(); err != nil {
log.Fatalf("启动隧道服务器失败: %v", err) return fmt.Errorf("启动隧道服务器失败: %v", err)
} }
defer tunnelServer.Stop()
} }
// 从数据库加载现有映射并启动转发器 // 从数据库加载现有映射并启动转发器
log.Println("加载现有端口映射...") log.Println("加载现有端口映射...")
mappings, err := database.GetAllMappings() mappings, err := database.GetAllMappings()
if err != nil { if err != nil {
log.Fatalf("加载端口映射失败: %v", err) return fmt.Errorf("加载端口映射失败: %v", err)
} }
for _, mapping := range mappings { 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) used := utils.PortCheck(mapping.SourcePort)
if used { if used {
log.Fatalf("警告: 端口 %d 已被占! 将退出程序", mapping.SourcePort) return fmt.Errorf("端口 %d 已被占用", mapping.SourcePort)
} }
var err error var err error
if mapping.UseTunnel { if mapping.UseTunnel {
// 隧道模式:检查隧道服务器是否可用 // 隧道模式:检查隧道服务器是否可用
if !cfg.Tunnel.Enabled || tunnelServer == nil { if !cfg.Tunnel.Enabled || s.tunnelServer == nil {
log.Printf("警告: 端口 %d 需要隧道模式但隧道服务未启用,跳过", mapping.SourcePort) log.Printf("警告: 端口 %d 需要隧道模式但隧道服务未启用,跳过", mapping.SourcePort)
continue continue
} }
err = fwdManager.AddTunnel(mapping.SourcePort, mapping.TargetHost, mapping.TargetPort, tunnelServer) err = s.fwdManager.AddTunnel(mapping.SourcePort, mapping.TargetHost, mapping.TargetPort, s.tunnelServer)
} else { } else {
// 直接模式 // 直接模式
err = fwdManager.Add(mapping.SourcePort, mapping.TargetHost, mapping.TargetPort) err = s.fwdManager.Add(mapping.SourcePort, mapping.TargetHost, mapping.TargetPort)
} }
if err != nil { if err != nil {
log.Printf("警告: 启动端口 %d 的转发失败: %v", mapping.SourcePort, err) log.Printf("警告: 启动端口 %d 的转发失败: %v", mapping.SourcePort, err)
} }
@ -91,61 +96,174 @@ func main() {
// 创建 HTTP API 处理器 // 创建 HTTP API 处理器
log.Println("初始化 HTTP API...") log.Println("初始化 HTTP API...")
apiHandler := api.NewHandler( s.apiHandler = api.NewHandler(database, s.fwdManager, s.tunnelServer)
database,
fwdManager,
tunnelServer,
// cfg.PortRange.From,
// cfg.PortRange.End,
)
// 启动 HTTP API 服务器 // 启动 HTTP API 服务器
go func() { 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) 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.Println("===========================================")
log.Printf("服务器启动成功!") 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 API: http://localhost:%d", cfg.API.ListenPort)
// log.Printf("调试接口: http://localhost:%d/debug/pprof/", pprofPort)
if cfg.Tunnel.Enabled { if cfg.Tunnel.Enabled {
log.Printf("隧道服务: 端口 %d", cfg.Tunnel.ListenPort) log.Printf("隧道服务: 端口 %d", cfg.Tunnel.ListenPort)
} }
log.Println("===========================================") log.Println("===========================================")
// 等待中断信号 // 等待中断信号
sigChan := make(chan os.Signal, 1) s.sigChan = make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) signal.Notify(s.sigChan, os.Interrupt, syscall.SIGTERM)
<-s.sigChan
<-sigChan return nil
log.Println("\n接收到关闭信号正在优雅关闭...") }
func (s *serverService) Stop() error {
log.Println("接收到关闭信号,正在优雅关闭...")
// 创建关闭上下文
// 停止所有转发器 // 停止所有转发器
log.Println("停止所有端口转发...") if s.fwdManager != nil {
fwdManager.StopAll() 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("服务器已关闭") 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
}