//go:build !windows package deploy import ( "os" "os/exec" "strconv" "strings" ) var serviceDiscoverUnits = []string{ "docker", "docker.service", "ssh", "sshd", "jenkins", "gitlab-runner", "cloudflared", "fail2ban", "ufw", "firewalld", } func probeLocalServices() []ServiceGraphEntry { var entries []ServiceGraphEntry for _, unit := range serviceDiscoverUnits { status := "not_found" if activeOut, err := exec.Command("systemctl", "is-active", unit).CombinedOutput(); err == nil { active := strings.TrimSpace(string(activeOut)) switch active { case "active": status = "running" case "inactive", "failed", "dead": status = "stopped" default: if active != "unknown" { status = "stopped" } } } if status == "not_found" { continue } entries = append(entries, entryWithLane(unit, 0, "local_service")) } if _, err := os.Stat("/var/run/docker.sock"); err == nil { entries = append(entries, entryWithLane("docker", 0, "passive_hint")) } if out, err := exec.Command("ss", "-lnt").CombinedOutput(); err == nil { entries = append(entries, parseSSListening(string(out))...) } else if out, err := exec.Command("netstat", "-lnt").CombinedOutput(); err == nil { entries = append(entries, parseNetstatListening(string(out))...) } return dedupeEntries(entries) } func collectPassiveHints() []string { var hints []string if _, err := os.Stat("/var/run/docker.sock"); err == nil { hints = append(hints, "docker_socket") } for _, path := range []string{ "/var/lib/gitlab-runner", "/etc/gitlab-runner", "/var/lib/jenkins", } { if _, err := os.Stat(path); err == nil { hints = append(hints, "runner_path:"+path) } } return hints } func parseSSListening(text string) []ServiceGraphEntry { var entries []ServiceGraphEntry for _, line := range strings.Split(text, "\n") { fields := strings.Fields(line) if len(fields) < 4 { continue } local := fields[3] port := parseListenPort(local) if port == 0 { continue } name := portServiceNames[port] if name == "" { name = "tcp/" + strconv.Itoa(port) } entries = append(entries, entryWithLane(name, port, "passive_hint")) } return entries } func parseNetstatListening(text string) []ServiceGraphEntry { var entries []ServiceGraphEntry for _, line := range strings.Split(text, "\n") { if !strings.Contains(line, "LISTEN") { continue } fields := strings.Fields(line) if len(fields) < 4 { continue } local := fields[3] port := parseListenPort(local) if port == 0 { continue } name := portServiceNames[port] if name == "" { name = "tcp/" + strconv.Itoa(port) } entries = append(entries, entryWithLane(name, port, "passive_hint")) } return entries } func parseListenPort(local string) int { // formats: *:22, 0.0.0.0:445, [::]:8080 if i := strings.LastIndex(local, ":"); i >= 0 { portStr := strings.TrimSuffix(local[i+1:], "]") if n, err := strconv.Atoi(portStr); err == nil { return n } } return 0 }