Initial Go project built with Bubble Tea and Lip Gloss: - Live preview and management of /etc/update-motd.d scripts - Embedded presets (ubuntu-default, minimal, server-dashboard) - Bundled POSIX sh style scripts (brand header, sysinfo panel, footer) - Snapshot/restore safety around applying changes - CLI subcommands preview, apply, restore, list-presets - Unit tests for detect, sanitize, and apply logic
74 lines
2 KiB
Go
74 lines
2 KiB
Go
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
|
|
}
|