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/script.go
Normal file
74
internal/motd/script.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// Package motd enthält die Domain-Logik: Discovery der update-motd.d-Scripte,
|
||||
// Diff/Plan von Soll-Zuständen, das Ausführen des Plans und die Preview.
|
||||
package motd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
|
||||
"motd-assist/internal/assets"
|
||||
)
|
||||
|
||||
// Script ist ein Eintrag in /etc/update-motd.d.
|
||||
type Script struct {
|
||||
Name string // Dateiname inkl. Nummern-Präfix, z.B. "00-header"
|
||||
Path string // Absoluter Pfad
|
||||
Enabled bool // Executable-Bit gesetzt (run-parts führt nur diese aus)
|
||||
Bundled bool // von motd-assist installiert (Inhalt entspricht einer Vorlage)
|
||||
}
|
||||
|
||||
// MotdDir liefert den Pfad des update-motd.d-Verzeichnisses unter root.
|
||||
func MotdDir(root string) string {
|
||||
return filepath.Join(root, "etc", "update-motd.d")
|
||||
}
|
||||
|
||||
// run-parts im pam_motd-Kontext ignoriert Dateien mit Punkten; wir halten
|
||||
// uns an dieselbe Namenskonvention ([A-Za-z0-9_-]).
|
||||
var validName = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
|
||||
|
||||
// Discover listet alle Scripte im update-motd.d-Verzeichnis in
|
||||
// run-parts-Reihenfolge (lexikalische Sortierung). Gebündelte Scripte
|
||||
// werden per Inhaltsvergleich erkannt.
|
||||
func Discover(root string) ([]Script, error) {
|
||||
dir := MotdDir(root)
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("update-motd.d nicht lesbar: %w", err)
|
||||
}
|
||||
scripts := make([]Script, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
if e.IsDir() || !validName.MatchString(e.Name()) {
|
||||
continue
|
||||
}
|
||||
info, err := e.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
path := filepath.Join(dir, e.Name())
|
||||
s := Script{
|
||||
Name: e.Name(),
|
||||
Path: path,
|
||||
Enabled: info.Mode()&0o111 != 0,
|
||||
}
|
||||
if b, err := os.ReadFile(path); err == nil {
|
||||
_, s.Bundled = assets.MatchScript(string(b))
|
||||
}
|
||||
scripts = append(scripts, s)
|
||||
}
|
||||
sort.Slice(scripts, func(i, j int) bool { return scripts[i].Name < scripts[j].Name })
|
||||
return scripts, nil
|
||||
}
|
||||
|
||||
// EnabledCount zählt die aktiven Scripte.
|
||||
func EnabledCount(scripts []Script) int {
|
||||
n := 0
|
||||
for _, s := range scripts {
|
||||
if s.Enabled {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue