Files
zkcoi e9ca2f7d70 fix: rename manager module to git.zkcoi.com/zkcoi/Meshray-Manager
- go.mod module path matches repo zkcoi/Meshray-Manager (case-distinct from zkcoi/Meshray/core)
- rewrite internal imports meshray/{internal,web,pkg} -> Meshray-Manager/... (core refs kept)
- sync README.md / install.sh repo URLs; add CHANGELOG entry
2026-07-15 16:25:46 +08:00

365 lines
10 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"net"
"strconv"
"git.zkcoi.com/zkcoi/Meshray-Manager/internal/ctr"
"git.zkcoi.com/zkcoi/Meshray-Manager/internal/model"
"git.zkcoi.com/zkcoi/Meshray-Manager/internal/store/sqlite"
"golang.org/x/crypto/curve25519"
"gorm.io/gorm"
)
// DeviceService 设备管理服务
type DeviceService struct {
store *sqlite.Store
ctrClient ctr.Client // meshray-ctr 客户端
}
// NewDeviceService 创建设备服务实例
func NewDeviceService(store *sqlite.Store, ctrClient ctr.Client) *DeviceService {
return &DeviceService{
store: store,
ctrClient: ctrClient,
}
}
// GetDevice 获取设备详情
func (s *DeviceService) GetDevice(id uint64) (*model.Device, error) {
var device model.Device
err := s.store.DB().First(&device, id).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("设备不存在")
}
return nil, err
}
return &device, nil
}
// ListAllDevices 获取所有设备列表
func (s *DeviceService) ListAllDevices() ([]model.Device, error) {
var devices []model.Device
err := s.store.DB().Find(&devices).Error
return devices, err
}
// ListDevicesByNetwork 获取网络下的设备列表
func (s *DeviceService) ListDevicesByNetwork(networkID uint64) ([]model.Device, error) {
var devices []model.Device
err := s.store.DB().Where("network_id = ?", networkID).Find(&devices).Error
return devices, err
}
// CreateDeviceRequest 创建设备请求
type CreateDeviceRequest struct {
NetworkID uint64 `json:"network_id"`
Name string `json:"name"`
VirtualIP string `json:"virtual_ip"` // 可选,留空则自动分配
Description string `json:"description"` // 可选
}
// CreateDeviceResult 创建设备结果
type CreateDeviceResult struct {
Device *model.Device `json:"device"`
PrivateKey string `json:"private_key"` // 仅首次返回
ConfigText string `json:"config_text"` // WireGuard 配置文本
}
// CreateDevice 创建设备
func (s *DeviceService) CreateDevice(req *CreateDeviceRequest) (*CreateDeviceResult, error) {
// 验证网络是否存在
var network model.Network
if err := s.store.DB().First(&network, req.NetworkID).Error; err != nil {
return nil, errors.New("网络不存在")
}
// 检查设备名称是否重复
var existing model.Device
if err := s.store.DB().Where("network_id = ? AND name = ?", req.NetworkID, req.Name).First(&existing).Error; err == nil {
return nil, errors.New("设备名称已存在")
}
// 生成 WireGuard 密钥对(同时获取私钥)
privateKey, publicKey, err := generateWireGuardKeys()
if err != nil {
return nil, fmt.Errorf("生成密钥失败:%w", err)
}
// 生成预共享密钥
preSharedKey := generatePreSharedKey()
// 自动分配 IP(如果未提供)
virtualIP := req.VirtualIP
if virtualIP == "" {
virtualIP, err = s.allocateIP(req.NetworkID, network.SubnetIPv4)
if err != nil {
return nil, fmt.Errorf("分配 IP 失败:%w", err)
}
}
// 创建设备
device := &model.Device{
NetworkID: req.NetworkID,
Name: req.Name,
VirtualIP: virtualIP,
PublicKey: publicKey,
PresharedKey: preSharedKey,
Status: "offline",
}
if err := s.store.DB().Create(device).Error; err != nil {
return nil, err
}
// P2 阶段 - 调用 meshray-ctr 添加 Peer
if s.ctrClient != nil {
allowedIP := device.VirtualIP + "/32"
if err := s.ctrClient.AddPeer(device.NetworkID, device.PublicKey, allowedIP); err != nil {
// 记录错误但不影响数据库操作(允许降级)
}
}
// 生成 WireGuard 配置文本
configText := s.deviceGenerateConfig(device, &network, privateKey)
return &CreateDeviceResult{
Device: device,
PrivateKey: privateKey,
ConfigText: configText,
}, nil
}
// UpdateDevice 更新设备
func (s *DeviceService) UpdateDevice(id uint64, updates map[string]interface{}) (*model.Device, error) {
var device model.Device
if err := s.store.DB().First(&device, id).Error; err != nil {
return nil, errors.New("设备不存在")
}
if err := s.store.DB().Model(&device).Updates(updates).Error; err != nil {
return nil, err
}
return &device, nil
}
// DeleteDevice 删除设备
func (s *DeviceService) DeleteDevice(id uint64) error {
// ✅ 使用事务保证数据一致性
tx := s.store.DB().Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
var device model.Device
if err := tx.First(&device, id).Error; err != nil {
tx.Rollback()
return errors.New("设备不存在")
}
// ✅ 如果设备在线,先断开连接(P2 阶段)
if s.ctrClient != nil {
if err := s.ctrClient.RemovePeer(device.NetworkID, device.PublicKey); err != nil {
// 记录警告但不阻断删除流程
fmt.Printf("⚠️ 从 WireGuard 移除 Peer 失败:network_id=%d, error=%v\n", device.NetworkID, err)
}
}
// ✅ 清理相关路由和配置(P2 阶段)
// ✅ P3 阶段 - 当前无额外路由配置,暂不实现
// 未来如果需要,可以在这里添加
// 删除设备
if err := tx.Delete(&device, id).Error; err != nil {
tx.Rollback()
return err
}
// 提交事务
if err := tx.Commit().Error; err != nil {
return err
}
return nil
}
// allocateIP 自动分配 IP 地址
func (s *DeviceService) allocateIP(networkID uint64, subnet string) (string, error) {
// 解析子网
_, ipNet, err := net.ParseCIDR(subnet)
if err != nil {
return "", fmt.Errorf("解析子网失败:%w", err)
}
// 检查是否是 IPv4 地址
if ipNet.IP.To4() == nil {
return "", errors.New("暂不支持 IPv6 地址分配")
}
// 查询已分配的所有 IP
var devices []model.Device
if err := s.store.DB().Where("network_id = ?", networkID).Find(&devices).Error; err != nil {
return "", fmt.Errorf("查询已分配 IP 失败:%w", err)
}
// 构建已占用 IP 集合
occupiedIPs := make(map[string]bool)
for _, device := range devices {
occupiedIPs[device.VirtualIP] = true
}
// 从 .2 开始分配(.1 通常留给网关)
// 遍历整个子网范围查找可用 IP
ip := ipNet.IP.To4()
startIP := ip.Mask(ipNet.Mask).To4()
startIP[3]++ // 从 .1 开始
// 最多尝试 254 个 IP/24 子网)
for i := 1; i < 255; i++ {
candidateIP := make(net.IP, len(startIP))
copy(candidateIP, startIP)
candidateIP[3] = byte(i + 1) // 从 .2 开始
// 检查是否被占用
if !occupiedIPs[candidateIP.String()] {
return candidateIP.String(), nil
}
}
return "", errors.New("IP 地址已耗尽")
}
// getSettings 获取系统设置(辅助方法)
func (s *DeviceService) getSettings() (*model.SystemSetting, error) {
var setting model.SystemSetting
err := s.store.DB().First(&setting, 1).Error
if err != nil {
// 如果不存在,返回默认值
return &model.SystemSetting{
ServerPort: 51820,
}, nil
}
return &setting, nil
}
// generateWireGuardKeys 生成 WireGuard 密钥对
func generateWireGuardKeys() (privateKey, publicKey string, err error) {
// 生成私钥(32 字节随机数)
var privKeyBytes [32]byte
if _, err := rand.Read(privKeyBytes[:]); err != nil {
return "", "", err
}
// 确保私钥符合 Curve25519 要求
privKeyBytes[0] &= 248
privKeyBytes[31] &= 127
privKeyBytes[31] |= 64
// 从私钥推导公钥
var pubKeyBytes [32]byte
curve25519.ScalarBaseMult(&pubKeyBytes, &privKeyBytes)
// Base64 编码
privateKey = base64.StdEncoding.EncodeToString(privKeyBytes[:])
publicKey = base64.StdEncoding.EncodeToString(pubKeyBytes[:])
return privateKey, publicKey, nil
}
// generatePreSharedKey 生成预共享密钥
func generatePreSharedKey() string {
bytes := make([]byte, 32)
rand.Read(bytes)
return base64.StdEncoding.EncodeToString(bytes)
}
// deviceGenerateConfig 生成 WireGuard 配置文本(设备创建时使用)
func (s *DeviceService) deviceGenerateConfig(device *model.Device, network *model.Network, privateKey string) string {
settings, _ := s.getSettings()
config := "[Interface]\n"
config += "PrivateKey = " + privateKey + "\n"
config += "Address = " + device.VirtualIP + "/32\n"
config += fmt.Sprintf("MTU = %d\n\n", network.MTU)
config += "[Peer]\n"
if settings.ServerPublicKey != "" {
config += "PublicKey = " + settings.ServerPublicKey + "\n"
} else {
config += "PublicKey = <SERVER_PUBLIC_KEY>\n"
}
if settings.ServerIP != "" {
config += "Endpoint = " + settings.ServerIP + ":" + strconv.Itoa(settings.ServerPort) + "\n"
} else {
config += fmt.Sprintf("Endpoint = <SERVER_IP>:%d\n", settings.ServerPort)
}
config += "AllowedIPs = " + network.SubnetIPv4 + "\n"
config += "PersistentKeepalive = 25\n"
return config
}
// GenerateDeviceConfig 生成设备配置文件
func (s *DeviceService) GenerateDeviceConfig(deviceID uint64) (string, error) {
// 获取设备信息(包含网络)
device, err := s.GetDevice(deviceID)
if err != nil {
return "", err
}
// 生成 WireGuard 密钥对
privateKey, publicKey, err := generateWireGuardKeys()
if err != nil {
return "", fmt.Errorf("生成密钥失败:%w", err)
}
// 更新设备的公钥到数据库
if err := s.store.DB().Model(device).Update("public_key", publicKey).Error; err != nil {
return "", fmt.Errorf("保存公钥失败:%w", err)
}
// 生成完整的 WireGuard 配置
config := "# MeshRay Generated Configuration\n"
config += "# Device: " + device.Name + "\n"
config += "# Created: " + device.CreatedAt.Format("2006-01-02 15:04:05") + "\n\n"
// [Interface] 部分
config += "[Interface]\n"
config += "PrivateKey = " + privateKey + "\n"
config += "Address = " + device.VirtualIP + "/32\n"
config += "DNS = 8.8.8.8, 8.8.4.4\n\n"
// [Peer] 部分(服务端配置)
config += "# Server (MeshRay)\n"
config += "[Peer]\n"
// 从 Settings 读取服务端公钥
settings, _ := s.getSettings()
if settings.ServerPublicKey == "" {
return "", errors.New("请先在系统设置中配置服务端公钥")
}
config += "PublicKey = " + settings.ServerPublicKey + "\n"
if device.PresharedKey != "" {
config += "PresharedKey = " + device.PresharedKey + "\n"
}
config += "AllowedIPs = 0.0.0.0/0\n"
// 使用 Settings 中的 ServerIP 和 ServerPort
if settings.ServerIP == "" {
return "", errors.New("请先在系统设置中配置服务端 IP 地址")
}
config += "Endpoint = " + settings.ServerIP + ":" + strconv.Itoa(settings.ServerPort) + "\n"
config += "PersistentKeepalive = 25\n"
return config, nil
}