💥 feat(agent)!: 添加主机监控代理首版
* 实现周期采集、快照缓存、本地 HTTP API 和主动上报 * 补齐 Linux 和 Windows 主机、网络、资源、进程和 TCP 采集 * 添加服务生命周期管理、配置加载、构建脚本和测试覆盖 BREAKING CHANGE: network.ips 拆分为 network.ipv4s 和 network.ipv6s。
This commit is contained in:
26
internal/service/service.go
Normal file
26
internal/service/service.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package service
|
||||
|
||||
import "mfd-monitor-agent/internal/config"
|
||||
|
||||
// InstallOptions carries platform-specific service installation inputs.
|
||||
type InstallOptions struct {
|
||||
// ConfigPath is preserved in the service command line when a config file was supplied.
|
||||
ConfigPath string
|
||||
}
|
||||
|
||||
// Manager controls operating system service installation and lifecycle commands.
|
||||
type Manager interface {
|
||||
// Install registers the agent executable as an operating system service.
|
||||
Install(executable string, cfg config.Config, opts InstallOptions) error
|
||||
// Uninstall removes the operating system service registration.
|
||||
Uninstall(cfg config.Config) error
|
||||
// Start asks the operating system service manager to start the agent service.
|
||||
Start(cfg config.Config) error
|
||||
// Stop asks the operating system service manager to stop the agent service.
|
||||
Stop(cfg config.Config) error
|
||||
}
|
||||
|
||||
// NewManager returns the platform-specific service manager.
|
||||
func NewManager() Manager {
|
||||
return newManager()
|
||||
}
|
||||
82
internal/service/service_linux.go
Normal file
82
internal/service/service_linux.go
Normal file
@@ -0,0 +1,82 @@
|
||||
//go:build linux
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"mfd-monitor-agent/internal/config"
|
||||
)
|
||||
|
||||
var currentUID = os.Geteuid
|
||||
|
||||
// linuxManager manages a systemd service for the agent.
|
||||
type linuxManager struct{}
|
||||
|
||||
// newManager returns the Linux systemd-backed service manager.
|
||||
func newManager() Manager {
|
||||
return linuxManager{}
|
||||
}
|
||||
|
||||
// Install writes a systemd unit, reloads systemd, and enables the configured service.
|
||||
func (linuxManager) Install(executable string, cfg config.Config, opts InstallOptions) error {
|
||||
if currentUID() != 0 {
|
||||
return fmt.Errorf("install requires root")
|
||||
}
|
||||
unit := renderSystemdUnit(executable, opts.ConfigPath, cfg)
|
||||
path := "/etc/systemd/system/" + cfg.Service.Name + ".service"
|
||||
if err := os.WriteFile(path, []byte(unit), 0644); err != nil {
|
||||
return fmt.Errorf("write systemd unit: %w", err)
|
||||
}
|
||||
if err := exec.Command("systemctl", "daemon-reload").Run(); err != nil {
|
||||
return fmt.Errorf("systemctl daemon-reload: %w", err)
|
||||
}
|
||||
if err := exec.Command("systemctl", "enable", cfg.Service.Name).Run(); err != nil {
|
||||
return fmt.Errorf("systemctl enable: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// renderSystemdUnit builds the Linux systemd unit used by Install.
|
||||
func renderSystemdUnit(executable string, configPath string, cfg config.Config) string {
|
||||
args := []string{executable, "run"}
|
||||
if configPath != "" {
|
||||
args = append(args, "-config", configPath)
|
||||
}
|
||||
return fmt.Sprintf(`[Unit]
|
||||
Description=%s
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=%s
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
`, cfg.Service.Description, strings.Join(args, " "))
|
||||
}
|
||||
|
||||
// Uninstall disables the service, removes its unit file, and reloads systemd.
|
||||
func (linuxManager) Uninstall(cfg config.Config) error {
|
||||
_ = exec.Command("systemctl", "disable", "--now", cfg.Service.Name).Run()
|
||||
if err := os.Remove("/etc/systemd/system/" + cfg.Service.Name + ".service"); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return exec.Command("systemctl", "daemon-reload").Run()
|
||||
}
|
||||
|
||||
// Start asks systemd to start the configured service.
|
||||
func (linuxManager) Start(cfg config.Config) error {
|
||||
return exec.Command("systemctl", "start", cfg.Service.Name).Run()
|
||||
}
|
||||
|
||||
// Stop asks systemd to stop the configured service.
|
||||
func (linuxManager) Stop(cfg config.Config) error {
|
||||
return exec.Command("systemctl", "stop", cfg.Service.Name).Run()
|
||||
}
|
||||
37
internal/service/service_linux_test.go
Normal file
37
internal/service/service_linux_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
//go:build linux
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"mfd-monitor-agent/internal/config"
|
||||
)
|
||||
|
||||
// TestRenderSystemdUnitIncludesConfigPath verifies install preserves the config flag.
|
||||
func TestRenderSystemdUnitIncludesConfigPath(t *testing.T) {
|
||||
cfg := config.Default()
|
||||
cfg.Service.Description = "MFD Agent"
|
||||
|
||||
unit := renderSystemdUnit("/opt/agent", "/etc/mfd-agent.json", cfg)
|
||||
|
||||
if !strings.Contains(unit, "ExecStart=/opt/agent run -config /etc/mfd-agent.json") {
|
||||
t.Fatalf("unit missing config path:\n%s", unit)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLinuxInstallRequiresRoot verifies Linux install rejects non-root callers.
|
||||
func TestLinuxInstallRequiresRoot(t *testing.T) {
|
||||
oldUID := currentUID
|
||||
t.Cleanup(func() { currentUID = oldUID })
|
||||
currentUID = func() int { return 1000 }
|
||||
|
||||
err := linuxManager{}.Install("/opt/agent", config.Default(), InstallOptions{})
|
||||
if err == nil {
|
||||
t.Fatal("expected root requirement error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "root") {
|
||||
t.Fatalf("error = %v", err)
|
||||
}
|
||||
}
|
||||
38
internal/service/service_other.go
Normal file
38
internal/service/service_other.go
Normal file
@@ -0,0 +1,38 @@
|
||||
//go:build !linux && !windows
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"mfd-monitor-agent/internal/config"
|
||||
)
|
||||
|
||||
// unsupportedManager reports service lifecycle operations as unsupported on this platform.
|
||||
type unsupportedManager struct{}
|
||||
|
||||
// newManager returns a manager that fails all service operations on unsupported platforms.
|
||||
func newManager() Manager {
|
||||
return unsupportedManager{}
|
||||
}
|
||||
|
||||
// Install reports that service installation is unsupported on this platform.
|
||||
func (unsupportedManager) Install(string, config.Config, InstallOptions) error {
|
||||
return fmt.Errorf("service install is not implemented for %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
// Uninstall reports that service removal is unsupported on this platform.
|
||||
func (unsupportedManager) Uninstall(config.Config) error {
|
||||
return fmt.Errorf("service uninstall is not implemented for %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
// Start reports that service start is unsupported on this platform.
|
||||
func (unsupportedManager) Start(config.Config) error {
|
||||
return fmt.Errorf("service start is not implemented for %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
// Stop reports that service stop is unsupported on this platform.
|
||||
func (unsupportedManager) Stop(config.Config) error {
|
||||
return fmt.Errorf("service stop is not implemented for %s", runtime.GOOS)
|
||||
}
|
||||
43
internal/service/service_windows.go
Normal file
43
internal/service/service_windows.go
Normal file
@@ -0,0 +1,43 @@
|
||||
//go:build windows
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"mfd-monitor-agent/internal/config"
|
||||
)
|
||||
|
||||
// windowsManager manages the agent through the Windows Service Control Manager.
|
||||
type windowsManager struct{}
|
||||
|
||||
// newManager returns the Windows service manager.
|
||||
func newManager() Manager {
|
||||
return windowsManager{}
|
||||
}
|
||||
|
||||
// Install registers the agent executable as an auto-start Windows Service.
|
||||
func (windowsManager) Install(executable string, cfg config.Config, opts InstallOptions) error {
|
||||
args := []string{`"` + executable + `"`, "run"}
|
||||
if opts.ConfigPath != "" {
|
||||
args = append(args, "-config", `"`+opts.ConfigPath+`"`)
|
||||
}
|
||||
return exec.Command("sc.exe", "create", cfg.Service.Name, "binPath=", strings.Join(args, " "), "DisplayName=", cfg.Service.DisplayName, "start=", "auto").Run()
|
||||
}
|
||||
|
||||
// Uninstall stops and removes the configured Windows Service.
|
||||
func (windowsManager) Uninstall(cfg config.Config) error {
|
||||
_ = exec.Command("sc.exe", "stop", cfg.Service.Name).Run()
|
||||
return exec.Command("sc.exe", "delete", cfg.Service.Name).Run()
|
||||
}
|
||||
|
||||
// Start asks the Windows Service Control Manager to start the service.
|
||||
func (windowsManager) Start(cfg config.Config) error {
|
||||
return exec.Command("sc.exe", "start", cfg.Service.Name).Run()
|
||||
}
|
||||
|
||||
// Stop asks the Windows Service Control Manager to stop the service.
|
||||
func (windowsManager) Stop(cfg config.Config) error {
|
||||
return exec.Command("sc.exe", "stop", cfg.Service.Name).Run()
|
||||
}
|
||||
Reference in New Issue
Block a user