package motd import ( "os" "path/filepath" "strings" ) // HostInfo beschreibt den erkannten MOTD-Zustand des Systems. type HostInfo struct { Hostname string Distro string // PRETTY_NAME aus /etc/os-release PAM bool // pam_motd konfiguriert (dynamischer MOTD aktiv) PAMWhere []string // Dateien in /etc/pam.d mit pam_motd-Eintrag DynamicPath string // vorhandene Cache-Datei der dynamischen MOTD ("" = keine) StaticMOTD bool // /etc/motd vorhanden und nicht leer } // DetectHost sammelt Host- und MOTD-Informationen unter root. // Fehler einzelner Erkennungen führen nicht zum Abbruch. func DetectHost(root string) HostInfo { info := HostInfo{Hostname: "unbekannt", Distro: "unbekannt"} if h, err := os.Hostname(); err == nil && h != "" { info.Hostname = strings.SplitN(h, ".", 2)[0] } if pretty, ok := osReleaseValue(filepath.Join(root, "etc", "os-release"), "PRETTY_NAME"); ok { info.Distro = pretty } for _, svc := range []string{"sshd", "login", "remote"} { b, err := os.ReadFile(filepath.Join(root, "etc", "pam.d", svc)) if err != nil { continue } for _, line := range strings.Split(string(b), "\n") { line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "#") { continue } if strings.Contains(line, "pam_motd") { info.PAM = true info.PAMWhere = append(info.PAMWhere, svc) break } } } for _, cand := range []string{"run/motd.dynamic", "run/motd"} { p := filepath.Join(root, cand) if fi, err := os.Stat(p); err == nil && !fi.IsDir() && fi.Size() > 0 { info.DynamicPath = p break } } if fi, err := os.Stat(filepath.Join(root, "etc", "motd")); err == nil && !fi.IsDir() && fi.Size() > 0 { info.StaticMOTD = true } return info } func osReleaseValue(path, key string) (string, bool) { b, err := os.ReadFile(path) if err != nil { return "", false } for _, line := range strings.Split(string(b), "\n") { if v, ok := strings.CutPrefix(strings.TrimSpace(line), key+"="); ok { return strings.Trim(v, `"'`), true } } return "", false }