mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
199 lines
6.7 KiB
Go
199 lines
6.7 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Package agent 装载 OpenFlare 边缘 agent 插件:openresty/WAF 运行时管理、
|
|
// 心跳同步、配置下发与 WebSocket 控制通道,以 Cordis 驱动形态在 profile "agent" 下运行。
|
|
package agent
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
|
|
"Wavelet/core"
|
|
"Wavelet/openflare/plugins/agent/agent"
|
|
"Wavelet/openflare/plugins/agent/config"
|
|
"Wavelet/openflare/plugins/agent/geoipupdate"
|
|
"Wavelet/openflare/plugins/agent/heartbeat"
|
|
"Wavelet/openflare/plugins/agent/httpclient"
|
|
"Wavelet/openflare/plugins/agent/nginx"
|
|
"Wavelet/openflare/plugins/agent/runtimeuser"
|
|
"Wavelet/openflare/plugins/agent/state"
|
|
syncservice "Wavelet/openflare/plugins/agent/sync"
|
|
"Wavelet/openflare/plugins/agent/updater"
|
|
"Wavelet/openflare/plugins/agent/wsclient"
|
|
"Wavelet/pkg/util"
|
|
)
|
|
|
|
// DriverTypeAgent 是边缘 agent 守护进程专属的驱动类型:
|
|
// 只有 profile 与之相等时内核才会 Start/Stop 本插件。
|
|
const DriverTypeAgent core.DriverType = "agent"
|
|
|
|
// Plugin 实现 core.Plugin 与 core.Driver,承载 agent 进程的全部装配与生命周期。
|
|
type Plugin struct {
|
|
configPath string
|
|
|
|
runner *agent.Runner
|
|
geo *geoipupdate.Updater
|
|
done chan error
|
|
started bool
|
|
}
|
|
|
|
// New 创建 agent 插件,configPath 指向其 JSON 配置文件。
|
|
func New(configPath string) *Plugin {
|
|
return &Plugin{configPath: configPath, done: make(chan error, 1)}
|
|
}
|
|
|
|
// Name 返回插件标识。
|
|
func (p *Plugin) Name() string { return "agent" }
|
|
|
|
// Type 返回驱动类型。
|
|
func (p *Plugin) Type() core.DriverType { return DriverTypeAgent }
|
|
|
|
// Apply 加载配置、确保运行环境、装配各服务并注册驱动。
|
|
func (p *Plugin) Apply(ctx *core.Context) error {
|
|
cfg, err := config.Load(p.configPath)
|
|
if err != nil {
|
|
return fmt.Errorf("load agent config: %w", err)
|
|
}
|
|
if err := runtimeuser.EnsureProcessUser(); err != nil {
|
|
return fmt.Errorf("ensure runtime user: %w", err)
|
|
}
|
|
if err := runtimeuser.EnsurePathOwnership(
|
|
cfg.DataDir, runtimeuser.DefaultDirPerm, runtimeuser.DefaultFilePerm,
|
|
); err != nil {
|
|
return fmt.Errorf("ensure data dir ownership, data_dir=%s: %w", cfg.DataDir, err)
|
|
}
|
|
|
|
nginxOptions := nginx.ExecutorOptions{
|
|
NginxPath: cfg.OpenrestyPath,
|
|
MainConfigPath: cfg.MainConfigPath,
|
|
RouteConfigPath: cfg.RouteConfigPath,
|
|
CertDir: cfg.CertDir,
|
|
NginxCertDir: cfg.OpenrestyCertDir,
|
|
LuaDir: cfg.LuaDir,
|
|
NginxLuaDir: cfg.OpenrestyLuaDir,
|
|
OpenrestyObservabilityPort: cfg.OpenrestyObservabilityPort,
|
|
}
|
|
cfg.ExtVersion = nginx.DetectVersion(context.Background(), nginxOptions)
|
|
logConfigLoaded(cfg)
|
|
|
|
runtimeManager := newRuntimeManager(cfg, nginxOptions)
|
|
if err := runtimeManager.EnsureLuaAssets(); err != nil {
|
|
return fmt.Errorf("ensure managed lua assets: %w", err)
|
|
}
|
|
|
|
client := httpclient.New(cfg.ServerURL, cfg.InitialAuthToken(), cfg.RequestTimeout.Duration())
|
|
wsClient := wsclient.New(cfg.ServerURL, cfg.InitialAuthToken(), cfg.RequestTimeout.Duration())
|
|
stateStore := state.NewStore(cfg.StatePath)
|
|
observabilityBuffer := state.NewObservabilityBufferStore(cfg.ObservabilityBufferPath)
|
|
syncService := syncservice.New(client, runtimeManager, stateStore)
|
|
syncService.SetPagesDir(cfg.PagesDir)
|
|
heartbeatService := heartbeat.New(client)
|
|
updateService := updater.New()
|
|
|
|
p.runner = &agent.Runner{
|
|
Config: cfg,
|
|
StateStore: stateStore,
|
|
HeartbeatCycle: &heartbeat.Cycle{
|
|
Config: cfg,
|
|
StateStore: stateStore,
|
|
ObservabilityBuffer: observabilityBuffer,
|
|
Heartbeat: heartbeatService,
|
|
Sync: syncService,
|
|
Updater: updateService,
|
|
},
|
|
HeartbeatService: heartbeatService,
|
|
SyncService: syncService,
|
|
RuntimeManager: runtimeManager,
|
|
WebSocketService: wsClient,
|
|
}
|
|
p.geo = newGeoIPUpdater(cfg)
|
|
|
|
return ctx.RegisterDriver(p)
|
|
}
|
|
|
|
// Start 预热 GeoIP 库并拉起心跳/同步/控制通道主循环。
|
|
//
|
|
// runner.Run 自身阻塞至 ctx 取消,因此放到独立 goroutine 中执行,
|
|
// 由 Stop 收敛其结果——内核要求驱动 Start 不得阻塞。
|
|
func (p *Plugin) Start(ctx context.Context) error {
|
|
if err := p.geo.EnsureInitialDatabases(ctx); err != nil {
|
|
slog.Warn("failed to prepare GeoIP databases before agent startup", "error", err)
|
|
}
|
|
util.Go(func() { p.geo.Run(ctx) })
|
|
|
|
util.Go(func() { p.done <- p.runner.Run(ctx) })
|
|
p.started = true
|
|
slog.Info("agent process started")
|
|
return nil
|
|
}
|
|
|
|
// Stop 等待主循环退出并返回其结果;ctx 超时时报告超时而非静默成功。
|
|
func (p *Plugin) Stop(ctx context.Context) error {
|
|
if !p.started {
|
|
return nil
|
|
}
|
|
select {
|
|
case err := <-p.done:
|
|
p.started = false
|
|
if err != nil && !errors.Is(err, context.Canceled) {
|
|
return err
|
|
}
|
|
slog.Info("agent process stopped")
|
|
return nil
|
|
case <-ctx.Done():
|
|
p.started = false
|
|
return fmt.Errorf("agent shutdown timeout: %w", ctx.Err())
|
|
}
|
|
}
|
|
|
|
// newGeoIPUpdater 装配国家/城市两套 mmdb 的下载与周期更新。
|
|
func newGeoIPUpdater(cfg *config.Config) *geoipupdate.Updater {
|
|
return &geoipupdate.Updater{
|
|
MMDBPath: cfg.MMDBPath,
|
|
DownloadURL: cfg.MMDBDownloadURL,
|
|
CityMMDBPath: cfg.CityMMDBPath,
|
|
CityDownloadURL: cfg.CityMMDBDownloadURL,
|
|
UpdateInterval: cfg.MMDBUpdateInterval.Duration(),
|
|
}
|
|
}
|
|
|
|
// newRuntimeManager 构造 openresty 配置与证书/Lua 资产的运行时管理器。
|
|
func newRuntimeManager(cfg *config.Config, nginxOptions nginx.ExecutorOptions) *nginx.Manager {
|
|
return &nginx.Manager{
|
|
MainConfigPath: cfg.MainConfigPath,
|
|
RouteConfigPath: cfg.RouteConfigPath,
|
|
AccessLogPath: cfg.AccessLogPath,
|
|
CertDir: cfg.CertDir,
|
|
NginxCertDir: cfg.OpenrestyCertDir,
|
|
LuaDir: cfg.LuaDir,
|
|
NginxLuaDir: cfg.OpenrestyLuaDir,
|
|
RuntimeConfigDir: cfg.RuntimeConfigDir,
|
|
MMDBPath: cfg.MMDBPath,
|
|
CityMMDBPath: cfg.CityMMDBPath,
|
|
PagesDir: cfg.PagesDir,
|
|
OpenrestyObservabilityListen: nginx.ObservabilityListenAddress(cfg.OpenrestyObservabilityPort),
|
|
OpenrestyObservabilityPort: cfg.OpenrestyObservabilityPort,
|
|
OpenrestyResolverDirective: "",
|
|
Executor: nginx.NewExecutor(nginxOptions),
|
|
}
|
|
}
|
|
|
|
func logConfigLoaded(cfg *config.Config) {
|
|
slog.Info("agent config loaded",
|
|
"server", cfg.ServerURL,
|
|
"node", cfg.NodeName,
|
|
"ip", cfg.NodeIP,
|
|
"heartbeat_interval", cfg.HeartbeatInterval,
|
|
"route_config", cfg.RouteConfigPath,
|
|
"access_log", cfg.AccessLogPath,
|
|
"cert_dir", cfg.CertDir,
|
|
"lua_dir", cfg.LuaDir,
|
|
"runtime_config_dir", cfg.RuntimeConfigDir,
|
|
"mmdb_path", cfg.MMDBPath,
|
|
"city_mmdb_path", cfg.CityMMDBPath,
|
|
)
|
|
}
|