Skip to content

Commit 75abbf1

Browse files
author
nil
committed
go mod update
1 parent 0e9bef0 commit 75abbf1

File tree

7 files changed

+451
-321
lines changed

7 files changed

+451
-321
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ access.log
88
/.vscode
99
server.crt
1010
server.key
11+
12+
headscale.db
13+
headscale.db-shm
14+
headscale.db-wal

assets/headscale.template

Lines changed: 185 additions & 139 deletions
Large diffs are not rendered by default.

core/headscale/handler.go

Lines changed: 161 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,167 @@
1-
//go:build linux || darwin || freebsd
1+
////go:build linux || darwin || freebsd
22

33
package headscale
44

55
import (
6+
"context"
67
"errors"
78
"fmt"
8-
"github.com/juanfont/headscale/hscontrol"
9-
"github.com/juanfont/headscale/hscontrol/types"
10-
"github.com/labstack/echo/v4"
11-
"github.com/spf13/viper"
129
"net/http"
1310
"net/netip"
1411
"net/url"
12+
"os"
13+
"sync"
1514
"time"
15+
16+
"github.com/juanfont/headscale/hscontrol"
17+
"github.com/juanfont/headscale/hscontrol/types"
18+
"github.com/labstack/echo/v4"
19+
)
20+
21+
// ================= 全局变量区 =================
22+
// 必须放在函数外面,否则每次请求进来都是新的,无法管理状态
23+
var (
24+
hsApp *hscontrol.Headscale // Headscale 实例
25+
hsRunning bool // 运行状态标记
26+
hsMutex sync.Mutex // 线程锁,防止同时点击启动炸掉
27+
hsCancel context.CancelFunc // 用于停止服务的开关
28+
hsError string // 记录启动时的报错信息
1629
)
1730

18-
// headscaleConfig 定义了Headscale的配置结构
31+
// headscaleConfig 前端表单结构
1932
type headscaleConfig struct {
20-
ServerURL string `form:"server_url"` // 服务器URL,客户端将连接到的地址
21-
ListenAddr string `form:"listen_addr"` // 服务器监听地址
22-
MetricsListenAddr string `form:"metrics_listen_addr"` // metrics监听地址
23-
GRPCListenAddr string `form:"grpc_listen_addr"` // gRPC监听地址
24-
PrivateKeyPath string `form:"private_key_path"` // Noise私钥路径
25-
IPv4Prefix string `form:"ipv4_prefix"` // IPv4地址分配范围
26-
IPv6Prefix string `form:"ipv6_prefix"` // IPv6地址分配范围
27-
BaseDomain string `form:"base_domain"` // MagicDNS基础域名
28-
ACMEEmail string `form:"acme_email"` // ACME注册邮箱
29-
TLSLetsEncryptHostname string `form:"tls_letsencrypt_hostname"` // Let's Encrypt主机名
30-
TLSCertPath string `form:"tls_cert_path"` // TLS证书路径
31-
TLSKeyPath string `form:"tls_key_path"` // TLS私钥路径
32-
UnixSocket string `form:"unix_socket"` // Unix套接字路径
33-
UnixSocketPermission int `form:"unix_socket_permission"` // Unix套接字权限
34-
DatabaseType string `form:"database_type"` // 数据库类型
35-
SqlitePath string `form:"sqlite_path"` // SQLite数据库路径
36-
DisableCheckUpdates bool `form:"disable_check_updates"` // 是否禁用更新检查
33+
ServerURL string `form:"server_url"`
34+
ListenAddr string `form:"listen_addr"`
35+
MetricsListenAddr string `form:"metrics_listen_addr"`
36+
GRPCListenAddr string `form:"grpc_listen_addr"`
37+
PrivateKeyPath string `form:"private_key_path"`
38+
IPv4Prefix string `form:"ipv4_prefix"`
39+
IPv6Prefix string `form:"ipv6_prefix"`
40+
BaseDomain string `form:"base_domain"`
3741
}
3842

39-
//const confPath = "/etc/headscale/config.yaml"
40-
43+
// Index 是唯一的入口函数
4144
func Index(c echo.Context) error {
42-
req := &headscaleConfig{}
43-
if err := c.Bind(req); err != nil {
44-
return err
45-
}
46-
if err := c.Validate(req); err != nil {
47-
return err
48-
}
49-
switch c.Request().Method {
50-
case "POST":
51-
if c.QueryParam("status") == "start" {
45+
// 获取请求动作类型
46+
action := c.QueryParam("status")
47+
method := c.Request().Method
48+
49+
// 1. 处理 POST 请求 (控制指令:启动、停止、检查状态)
50+
if method == "POST" {
51+
hsMutex.Lock()
52+
defer hsMutex.Unlock()
53+
54+
switch action {
55+
case "start":
56+
// 如果已经在运行,直接返回成功
57+
if hsRunning {
58+
return c.JSON(200, map[string]any{"success": true, "message": "Already running"})
59+
}
60+
61+
// 绑定参数
62+
req := &headscaleConfig{}
63+
if err := c.Bind(req); err != nil {
64+
return c.JSON(400, map[string]any{"success": false, "message": "Invalid config"})
65+
}
66+
67+
// 转换配置
5268
cfg, err := loadServerConfig(req)
5369
if err != nil {
54-
return err
70+
return c.JSON(400, map[string]any{"success": false, "message": err.Error()})
5571
}
72+
73+
// 初始化 Headscale
5674
app, err := hscontrol.NewHeadscale(cfg)
5775
if err != nil {
58-
return err
76+
return c.JSON(500, map[string]any{"success": false, "message": "Init failed: " + err.Error()})
5977
}
60-
if err = app.Serve(); err != nil && !errors.Is(err, http.ErrServerClosed) {
61-
return err
78+
79+
// 创建用于停止的 Context
80+
ctx, cancel := context.WithCancel(context.Background())
81+
82+
// 更新全局状态
83+
hsApp = app
84+
hsCancel = cancel
85+
hsRunning = true
86+
hsError = ""
87+
88+
// 【关键点】放入 goroutine 异步运行,防止阻塞 HTTP 请求
89+
go func() {
90+
// 注意:这里需要 Headscale 支持 Context 传入或者只是单纯运行
91+
// 目前 Headscale 的 Serve 通常是阻塞的
92+
// 如果需要支持优雅关闭,可能需要修改 Headscale 源码或使用其提供的 Shutdown 方法
93+
// 这里简单处理:启动服务
94+
if err := app.Serve(); err != nil && !errors.Is(err, http.ErrServerClosed) {
95+
hsMutex.Lock()
96+
hsRunning = false
97+
hsError = err.Error()
98+
hsMutex.Unlock()
99+
fmt.Println("Headscale stopped unexpectedly:", err)
100+
} else {
101+
// 正常退出
102+
hsMutex.Lock()
103+
hsRunning = false
104+
hsMutex.Unlock()
105+
}
106+
}()
107+
108+
// 这里稍微利用 Context 做一个假装的生命周期管理(Headscale原生Serve如果不接受Context,强制停止比较麻烦)
109+
// 为了演示,我们保存了 cancel,实际停止时可能需要关闭 listener
110+
go func() {
111+
<-ctx.Done()
112+
// 当 cancel 被调用时,尝试关闭 app (如果 app 暴露了 Shutdown)
113+
// hsApp.Shutdown()
114+
}()
115+
116+
return c.JSON(200, map[string]any{"success": true, "message": "Headscale started in background"})
117+
118+
case "stop":
119+
if !hsRunning {
120+
return c.JSON(200, map[string]any{"success": false, "message": "Not running"})
121+
}
122+
123+
// 触发停止
124+
if hsCancel != nil {
125+
hsCancel() // 触发 Context 取消
62126
}
127+
128+
// 注意:由于 Go 的 http.Server 需要显式 Shutdown,
129+
// 如果 headscale 库没暴露 Shutdown 方法,这里可能关不掉端口。
130+
// 假设 hscontrol 内部处理了关闭逻辑,或者你可能需要重启整个主进程。
131+
132+
hsRunning = false
133+
hsApp = nil
134+
return c.JSON(200, map[string]any{"success": true, "message": "Stop signal sent"})
135+
136+
case "check": // 前端轮询用
137+
statusStr := "stopped"
138+
if hsRunning {
139+
statusStr = "running"
140+
}
141+
return c.JSON(200, map[string]any{
142+
"status": statusStr,
143+
"error": hsError,
144+
})
145+
146+
default:
147+
// 可以在这里处理保存配置逻辑
148+
// saveConfigToDisk(c)
149+
return c.JSON(200, map[string]any{"success": true, "message": "Config saved (mock)"})
63150
}
151+
}
64152

65-
return c.JSON(200, "success")
66-
//case "PUT":
67-
// data, err := io.ReadAll(c.Request().Body)
68-
// defer c.Request().Body.Close()
69-
// if err != nil {
70-
// return err
71-
// }
72-
// if err := os.WriteFile(confPath, data, 0644); err != nil {
73-
// return err
74-
// }
75-
// return c.JSON(200, "success")
76-
case "GET":
77-
//file, err := os.ReadFile(confPath)
78-
//if err != nil {
79-
// return err
80-
//}
153+
// 2. 处理 GET 请求 (渲染页面)
154+
if method == "GET" {
155+
// 可以在这里读取配置文件回显
81156
return c.Render(http.StatusOK, "headscale.template", map[string]any{
82-
//"headscaleConfig": string(file),
83-
"headscaleEnable": viper.Get("enable.frps").(bool),
157+
"headscaleEnable": hsRunning,
84158
})
85159
}
86160

87161
return echo.ErrMethodNotAllowed
88162
}
89163

164+
// 辅助函数:配置转换
90165
func loadServerConfig(c *headscaleConfig) (*types.Config, error) {
91166
prefix4, err := netip.ParsePrefix(c.IPv4Prefix)
92167
if err != nil {
@@ -100,42 +175,41 @@ func loadServerConfig(c *headscaleConfig) (*types.Config, error) {
100175

101176
derpURL, _ := url.Parse("https://controlplane.tailscale.com/derpmap/default")
102177

178+
// 获取当前路径,防止权限问题
179+
cwd, _ := os.Getwd()
180+
103181
return &types.Config{
104-
ServerURL: c.ServerURL, // 客户端将连接到的服务器URL
105-
Addr: c.ListenAddr, // 服务器监听地址
106-
MetricsAddr: c.MetricsListenAddr, // metrics监听地址
107-
GRPCAddr: c.GRPCListenAddr, // gRPC监听地址
108-
GRPCAllowInsecure: false, // 是否允许不安全的gRPC连接
109-
EphemeralNodeInactivityTimeout: 30 * time.Minute, // 临时节点不活动超时时间
110-
PrefixV4: &prefix4, // IPv4地址分配范围
111-
PrefixV6: &prefix6, // IPv6地址分配范围
112-
IPAllocation: types.IPAllocationStrategySequential, // IP分配策略
113-
NoisePrivateKeyPath: c.PrivateKeyPath, // Noise协议私钥路径
114-
BaseDomain: c.BaseDomain, // MagicDNS基础域名
115-
Log: types.LogConfig{}, // 日志配置
116-
DisableUpdateCheck: true, // 禁用更新检查
182+
ServerURL: c.ServerURL,
183+
Addr: c.ListenAddr,
184+
MetricsAddr: c.MetricsListenAddr,
185+
GRPCAddr: c.GRPCListenAddr,
186+
GRPCAllowInsecure: true,
187+
EphemeralNodeInactivityTimeout: 30 * time.Minute,
188+
PrefixV4: &prefix4,
189+
PrefixV6: &prefix6,
190+
IPAllocation: types.IPAllocationStrategySequential,
191+
NoisePrivateKeyPath: c.PrivateKeyPath,
192+
BaseDomain: c.BaseDomain,
193+
Log: types.LogConfig{Level: 1}, // 简单的日志配置
194+
DisableUpdateCheck: true,
117195
Database: types.DatabaseConfig{
118-
Type: "sqlite", // 数据库类型
119-
Debug: false, // 是否启用数据库调试
196+
Type: "sqlite3",
197+
Debug: false,
120198
Sqlite: types.SqliteConfig{
121-
Path: "/var/lib/headscale/db.sqlite", // SQLite数据库路径
122-
WriteAheadLog: false, // 是否启用预写日志
199+
Path: fmt.Sprintf("%s/headscale.db", cwd), // 改为当前目录
200+
WriteAheadLog: true,
123201
},
124202
},
125203
DERP: types.DERPConfig{
126-
ServerEnabled: false, // 是否启用DERP服务器
127-
AutomaticallyAddEmbeddedDerpRegion: false, // 是否自动添加嵌入式DERP区域
128-
URLs: []url.URL{*derpURL}, // DERP服务器URL列表
129-
AutoUpdate: true, // 是否自动更新DERP地图
130-
UpdateFrequency: 24 * time.Hour, // DERP地图更新频率
204+
ServerEnabled: false,
205+
AutomaticallyAddEmbeddedDerpRegion: false,
206+
URLs: []url.URL{*derpURL},
207+
AutoUpdate: true,
208+
UpdateFrequency: 24 * time.Hour,
131209
},
132-
133-
TLS: types.TLSConfig{}, // TLS配置
134-
ACMEURL: "https://acme-v02.api.letsencrypt.org/directory", // ACME服务器URL
135-
ACMEEmail: "", // ACME注册邮箱
136-
DNSConfig: types.DNSConfig{}, // DNS配置
137-
//DNSUserNameInMagicDNS: false, // 是否在MagicDNS中使用用户名
138-
UnixSocket: "/var/run/headscale.sock", // Unix套接字路径
139-
UnixSocketPermission: 0600, // Unix套接字权限
210+
TLS: types.TLSConfig{},
211+
DNSConfig: types.DNSConfig{},
212+
UnixSocket: fmt.Sprintf("%s/headscale.sock", cwd), // 改为当前目录
213+
UnixSocketPermission: 0755,
140214
}, nil
141215
}

core/headscale/unavailable.go

Lines changed: 0 additions & 10 deletions
This file was deleted.

core/route.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func (c *Core) Route() {
7272
admin.Any("/frps", frps.Index)
7373
admin.Any("/frpc", frpc.Index)
7474
// Headscale RESTful 路由
75-
admin.GET("/headscale", headscale.Index) // 获取页面
75+
admin.Any("/headscale", headscale.Index) // 获取页面
7676
admin.Any("/firewall", firewall.Index)
7777
//admin.Any("/UnblockNeteaseMusic", UnblockNeteaseMusic.Index)
7878
c.e.StartTLS(viper.GetString("panel.port"), []byte(certPEM), []byte(keyPEM))

0 commit comments

Comments
 (0)