Files
fleetforge/agents/forge-agent/internal/svcmgr/svcmgr.go
T

288 lines
8.0 KiB
Go

package svcmgr
import (
"fmt"
"os/exec"
"runtime"
"strings"
)
type ServiceInfo struct {
Name string `json:"name"`
Status string `json:"status"`
Enabled bool `json:"enabled"`
}
func ListServices() ([]ServiceInfo, error) {
switch runtime.GOOS {
case "windows":
return listWindowsServices()
case "darwin":
return listDarwinServices()
default:
return listLinuxServices()
}
}
func GetServiceStatus(name string) (string, bool, error) {
switch runtime.GOOS {
case "windows":
return getWindowsServiceStatus(name)
case "darwin":
return getDarwinServiceStatus(name)
default:
return getLinuxServiceStatus(name)
}
}
func StartService(name string) (string, error) {
switch runtime.GOOS {
case "windows":
return startWindowsService(name)
case "darwin":
return startDarwinService(name)
default:
return startLinuxService(name)
}
}
func StopService(name string) (string, error) {
switch runtime.GOOS {
case "windows":
return stopWindowsService(name)
case "darwin":
return stopDarwinService(name)
default:
return stopLinuxService(name)
}
}
func RestartService(name string) (string, error) {
switch runtime.GOOS {
case "windows":
return restartWindowsService(name)
case "darwin":
return restartDarwinService(name)
default:
return restartLinuxService(name)
}
}
func Reboot() error {
switch runtime.GOOS {
case "windows":
return exec.Command("shutdown", "/r", "/t", "0").Run()
case "darwin":
return exec.Command("sudo", "shutdown", "-r", "now").Run()
default:
return exec.Command("sudo", "reboot").Run()
}
}
func Shutdown() error {
switch runtime.GOOS {
case "windows":
return exec.Command("shutdown", "/s", "/t", "0").Run()
case "darwin":
return exec.Command("sudo", "shutdown", "-h", "now").Run()
default:
return exec.Command("sudo", "poweroff").Run()
}
}
func listWindowsServices() ([]ServiceInfo, error) {
out, err := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command",
"Get-Service -ErrorAction SilentlyContinue | ForEach-Object { '{0}`t{1}`t{2}' -f $_.Name, $_.Status, $_.StartType }").Output()
if err != nil {
return nil, fmt.Errorf("Get-Service failed: %w", err)
}
var services []ServiceInfo
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
fields := strings.Split(strings.TrimSpace(line), "\t")
if len(fields) < 3 {
continue
}
services = append(services, ServiceInfo{
Name: fields[0],
Status: strings.ToLower(fields[1]),
Enabled: strings.EqualFold(fields[2], "Automatic"),
})
}
return services, nil
}
func getWindowsServiceStatus(name string) (string, bool, error) {
out, err := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command",
fmt.Sprintf("$s = Get-Service -Name '%s' -ErrorAction Stop; '{0}`t{1}' -f $s.Status, $s.StartType", name)).Output()
if err != nil {
return "", false, fmt.Errorf("Get-Service failed: %w", err)
}
fields := strings.Split(strings.TrimSpace(string(out)), "\t")
if len(fields) < 2 {
return "", false, fmt.Errorf("unexpected Get-Service output: %s", strings.TrimSpace(string(out)))
}
return strings.ToLower(fields[0]), strings.EqualFold(fields[1], "Automatic"), nil
}
func startWindowsService(name string) (string, error) {
cmd := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command",
fmt.Sprintf("Start-Service -Name '%s' -ErrorAction Stop", name))
if out, err := cmd.CombinedOutput(); err != nil {
return string(out), fmt.Errorf("start failed: %w\nOutput: %s", err, string(out))
}
return "started", nil
}
func stopWindowsService(name string) (string, error) {
cmd := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command",
fmt.Sprintf("Stop-Service -Name '%s' -ErrorAction Stop", name))
if out, err := cmd.CombinedOutput(); err != nil {
return string(out), fmt.Errorf("stop failed: %w\nOutput: %s", err, string(out))
}
return "stopped", nil
}
func restartWindowsService(name string) (string, error) {
cmd := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command",
fmt.Sprintf("Restart-Service -Name '%s' -ErrorAction Stop", name))
if out, err := cmd.CombinedOutput(); err != nil {
return string(out), fmt.Errorf("restart failed: %w\nOutput: %s", err, string(out))
}
return "restarted", nil
}
func listDarwinServices() ([]ServiceInfo, error) {
out, err := exec.Command("launchctl", "list").Output()
if err != nil {
return nil, fmt.Errorf("launchctl list failed: %w", err)
}
var services []ServiceInfo
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n")[1:] {
fields := strings.Fields(line)
if len(fields) < 3 {
continue
}
status := "unknown"
if fields[0] != "-" {
status = "running"
}
services = append(services, ServiceInfo{
Name: fields[2],
Status: status,
})
}
return services, nil
}
func getDarwinServiceStatus(name string) (string, bool, error) {
out, err := exec.Command("launchctl", "list").Output()
if err != nil {
return "", false, fmt.Errorf("launchctl list failed: %w", err)
}
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n")[1:] {
fields := strings.Fields(line)
if len(fields) >= 3 && fields[2] == name {
if fields[0] != "-" {
return "running", true, nil
}
return "inactive", false, nil
}
}
return "not_found", false, nil
}
func startDarwinService(name string) (string, error) {
cmd := exec.Command("launchctl", "load", fmt.Sprintf("/Library/LaunchDaemons/%s.plist", name))
if out, err := cmd.CombinedOutput(); err != nil {
return string(out), fmt.Errorf("start failed: %w\nOutput: %s", err, string(out))
}
return "started", nil
}
func stopDarwinService(name string) (string, error) {
cmd := exec.Command("launchctl", "unload", fmt.Sprintf("/Library/LaunchDaemons/%s.plist", name))
if out, err := cmd.CombinedOutput(); err != nil {
return string(out), fmt.Errorf("stop failed: %w\nOutput: %s", err, string(out))
}
return "stopped", nil
}
func restartDarwinService(name string) (string, error) {
stopDarwinService(name)
return startDarwinService(name)
}
func listLinuxServices() ([]ServiceInfo, error) {
cmd := exec.Command("systemctl", "list-units", "--type=service", "--all", "--no-pager", "--no-legend", "--plain", "--no-accept-unknown")
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("systemctl list-units failed: %w", err)
}
var services []ServiceInfo
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
fields := strings.Fields(line)
if len(fields) < 4 {
continue
}
svcName := strings.TrimSuffix(fields[0], ".service")
status := fields[2]
enabled := fields[3] == "loaded"
services = append(services, ServiceInfo{
Name: svcName,
Status: status,
Enabled: enabled,
})
}
return services, nil
}
func getLinuxServiceStatus(name string) (string, bool, error) {
cmd := exec.Command("systemctl", "is-active", name)
out, err := cmd.Output()
status := strings.TrimSpace(string(out))
enabledCmd := exec.Command("systemctl", "is-enabled", name)
enabledOut, _ := enabledCmd.Output()
enabled := strings.TrimSpace(string(enabledOut)) == "enabled"
if err != nil && status != "inactive" {
return status, enabled, fmt.Errorf("systemctl status failed: %w", err)
}
return status, enabled, nil
}
func startLinuxService(name string) (string, error) {
cmd := exec.Command("sudo", "systemctl", "start", name)
out, err := cmd.CombinedOutput()
if err != nil {
return string(out), fmt.Errorf("start failed: %w\nOutput: %s", err, string(out))
}
return "started", nil
}
func stopLinuxService(name string) (string, error) {
cmd := exec.Command("sudo", "systemctl", "stop", name)
out, err := cmd.CombinedOutput()
if err != nil {
return string(out), fmt.Errorf("stop failed: %w\nOutput: %s", err, string(out))
}
return "stopped", nil
}
func restartLinuxService(name string) (string, error) {
cmd := exec.Command("sudo", "systemctl", "restart", name)
out, err := cmd.CombinedOutput()
if err != nil {
return string(out), fmt.Errorf("restart failed: %w\nOutput: %s", err, string(out))
}
return "restarted", nil
}