package motd import ( "bytes" "context" "fmt" "os" "os/exec" "path/filepath" "strings" "time" "motd-assist/internal/assets" ) // scriptTimeout begrenzt die Laufzeit eines einzelnen Preview-Scripts // (langsame Netzwerk-Scripte wie 50-motd-news blockieren sonst die TUI). const scriptTimeout = 2 * time.Second // PreviewScript ist ein Preview-Kandidat: entweder eine vorhandene Datei // (Path) oder eine noch nicht installierte gebündelte Vorlage (Source). type PreviewScript struct { Name string Path string Source string Enabled bool } // Render führt die aktivierten Scripte in Reihenfolge aus und liefert den // bereinigten MOTD-Output. Fehler einzelner Scripte werden als dezente // Hinweiszeile eingebaut statt die ganze Preview abbrechen. func Render(scripts []PreviewScript, width int) string { var parts []string for _, s := range scripts { if !s.Enabled { continue } path := s.Path if path == "" { if s.Source == "" { continue } staged, cleanup, err := stagePreview(s.Source, s.Name) if err != nil { parts = append(parts, dimLine(fmt.Sprintf("%s: %v", s.Name, err))) continue } path = staged defer cleanup() } addPart(&parts, runScript(path, width), width) } return strings.Join(parts, "\n\n") } func addPart(parts *[]string, raw string, width int) { if text := strings.TrimSpace(Sanitize(raw, width)); text != "" { *parts = append(*parts, text) } } func runScript(path string, width int) string { ctx, cancel := context.WithTimeout(context.Background(), scriptTimeout) defer cancel() cmd := exec.CommandContext(ctx, path) cmd.Env = append(os.Environ(), fmt.Sprintf("COLUMNS=%d", width), "TERM=xterm-256color", ) cmd.Dir = "/" var buf bytes.Buffer cmd.Stdout = &buf cmd.Stderr = &buf if err := cmd.Run(); err != nil { if ctx.Err() == context.DeadlineExceeded { return dimLine(fmt.Sprintf("%s: Timeout", filepath.Base(path))) } if buf.Len() == 0 { return dimLine(fmt.Sprintf("%s: nicht ausführbar (%v)", filepath.Base(path), err)) } // Exit-Status != 0 ist bei MOTD-Scripten üblich; Output behalten. } return buf.String() } // stagePreview kopiert eine gebündelte Vorlage in ein Temporärverzeichnis, // damit die Live-Vorschau sie vor der Installation zeigen kann. func stagePreview(source, name string) (string, func(), error) { content, ok := assets.ScriptContent(source) if !ok { return "", nil, fmt.Errorf("unbekannte Vorlage %q", source) } dir, err := os.MkdirTemp("", "motd-assist-") if err != nil { return "", nil, err } path := filepath.Join(dir, name) if err := os.WriteFile(path, []byte(content), 0o755); err != nil { os.RemoveAll(dir) return "", nil, err } return path, func() { os.RemoveAll(dir) }, nil } func dimLine(s string) string { return "\x1b[2m" + s + "\x1b[0m" }