feat: add motd-assist TUI for managing dynamic MOTDs
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
This commit is contained in:
commit
d56659e21a
34 changed files with 3260 additions and 0 deletions
74
internal/motd/detect.go
Normal file
74
internal/motd/detect.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue