package collector import ( "os/exec" "path/filepath" "strings" ) type ServiceInfo struct { Name string `json:"name"` Description string `json:"description"` Status string `json:"status"` } func CollectServices() []ServiceInfo { // Versuche zuerst pluginctl -s (OPNsense-spezifisch) services := tryPluginctl() if len(services) > 0 { return services } // Fallback: service -e listet aktivierte Services return tryServiceList() } // FreeBSD boot/init scripts that run once and are not real services var ignoredServices = map[string]bool{ "bgfsck": true, "cleanvar": true, "cleartmp": true, "devmatch": true, "dmesg": true, "gptboot": true, "hostid": true, "hostid_save": true, "kldxref": true, "mixer": true, "motd": true, "netif": true, "newsyslog": true, "os-release": true, "rctl": true, "resolv": true, "savecore": true, "utx": true, "var_run": true, "virecover": true, "adjkerntz": true, "automount": true, "automountd": true, "autounmountd": true, "cron": true, "devd": true, "dhclient": true, "fsck": true, "growfs": true, "iovctl": true, "ip6addrctl": true, "ipsec": true, "kld": true, "ldconfig": true, "linux": true, "local": true, "localpkg": true, "lockd": true, "mdconfig": true, "mdconfig2": true, "mountcritlocal": true, "mountcritremote": true, "mountlate": true, "msgs": true, "nsswitch": true, "ntpdate": true, "pf": true, "pflog": true, "powerd": true, "random": true, "root": true, "routing": true, "securelevel": true, "serial": true, "statd": true, "swap": true, "swaplate": true, "syscons": true, "sysctl": true, "tmp": true, "ugidfw": true, "zfs": true, "zvol": true, } func tryPluginctl() []ServiceInfo { out, err := exec.Command("/usr/local/sbin/pluginctl", "-s").Output() if err != nil { return nil } var services []ServiceInfo for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { name := strings.TrimSpace(line) if name == "" || ignoredServices[name] { continue } // pluginctl -s gibt nur Service-Namen, Status einzeln pruefen status := "stopped" statusOut, err := exec.Command("service", name, "status").CombinedOutput() if err == nil && strings.Contains(string(statusOut), "is running") { status = "running" } // Beschreibung aus rc.d Script extrahieren (# PROVIDE: name) desc := "" descOut, _ := exec.Command("grep", "-h", "PROVIDE:", "/etc/rc.d/"+name, "/usr/local/etc/rc.d/"+name).Output() if len(descOut) > 0 { parts := strings.SplitN(string(descOut), "PROVIDE:", 2) if len(parts) == 2 { desc = strings.TrimSpace(parts[1]) } } services = append(services, ServiceInfo{ Name: name, Description: desc, Status: status, }) } return services } func tryServiceList() []ServiceInfo { out, err := exec.Command("service", "-e").Output() if err != nil { return nil } var services []ServiceInfo for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { if line == "" { continue } // service -e gibt volle Pfade: /etc/rc.d/sshd, /usr/local/etc/rc.d/openvpn name := filepath.Base(strings.TrimSpace(line)) // Status pruefen status := "running" // service -e listet nur laufende Services statusOut, err := exec.Command("service", name, "status").Output() if err != nil { status = "stopped" } else if strings.Contains(string(statusOut), "not running") { status = "stopped" } services = append(services, ServiceInfo{ Name: name, Status: status, }) } return services }