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
127 lines
3.3 KiB
Go
127 lines
3.3 KiB
Go
// Package preset definiert das Preset-Schema und das Laden/Speichern
|
|
// von Built-in- und User-Presets.
|
|
package preset
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
|
|
"motd-assist/internal/assets"
|
|
)
|
|
|
|
// Entry beschreibt den Soll-Zustand eines Scripts innerhalb eines Presets.
|
|
// Die Reihenfolge im Scripts-Array bestimmt die Ziel-Reihenfolge im MOTD.
|
|
type Entry struct {
|
|
Name string `json:"name"` // Zieldateiname, z.B. "10-brand-header"
|
|
Enabled bool `json:"enabled"` // Executable-Bit gesetzt
|
|
Install string `json:"install,omitempty"` // gebündelte Vorlage, leer = vorhandenes Script
|
|
}
|
|
|
|
// Preset ist eine benannte MOTD-Konfiguration.
|
|
type Preset struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description,omitempty"`
|
|
Scripts []Entry `json:"scripts"`
|
|
|
|
Source string `json:"-"` // "builtin" oder "user"
|
|
}
|
|
|
|
var safeName = regexp.MustCompile(`[^a-zA-Z0-9._-]+`)
|
|
|
|
// Dir liefert das User-Preset-Verzeichnis (XDG-konform).
|
|
func Dir() (string, error) {
|
|
if d := os.Getenv("XDG_CONFIG_HOME"); d != "" {
|
|
return filepath.Join(d, "motd-assist", "presets"), nil
|
|
}
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return filepath.Join(home, ".config", "motd-assist", "presets"), nil
|
|
}
|
|
|
|
// LoadAll liefert alle Presets: Built-ins, durch User-Presets mit
|
|
// gleichem Namen überschrieben. Sortiert nach Name.
|
|
func LoadAll() ([]Preset, error) {
|
|
byName := make(map[string]Preset)
|
|
|
|
for file, content := range assets.PresetFiles() {
|
|
var p Preset
|
|
if err := json.Unmarshal([]byte(content), &p); err != nil {
|
|
return nil, fmt.Errorf("builtin preset %s: %w", file, err)
|
|
}
|
|
p.Source = "builtin"
|
|
byName[p.Name] = p
|
|
}
|
|
|
|
dir, err := Dir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
entries, err := os.ReadDir(dir)
|
|
if err == nil {
|
|
for _, e := range entries {
|
|
if e.IsDir() || filepath.Ext(e.Name()) != ".json" {
|
|
continue
|
|
}
|
|
b, err := os.ReadFile(filepath.Join(dir, e.Name()))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
var p Preset
|
|
if err := json.Unmarshal(b, &p); err != nil {
|
|
continue
|
|
}
|
|
p.Source = "user"
|
|
byName[p.Name] = p
|
|
}
|
|
} else if !errors.Is(err, os.ErrNotExist) {
|
|
return nil, err
|
|
}
|
|
|
|
presets := make([]Preset, 0, len(byName))
|
|
for _, p := range byName {
|
|
presets = append(presets, p)
|
|
}
|
|
sort.Slice(presets, func(i, j int) bool { return presets[i].Name < presets[j].Name })
|
|
return presets, nil
|
|
}
|
|
|
|
// SaveUser schreibt ein Preset ins User-Verzeichnis (überschreibt Built-ins
|
|
// mit gleichem Namen zur Laufzeit).
|
|
func SaveUser(p Preset) (string, error) {
|
|
if p.Name == "" {
|
|
return "", errors.New("Preset-Name fehlt")
|
|
}
|
|
dir, err := Dir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
return "", err
|
|
}
|
|
b, err := json.MarshalIndent(p, "", " ")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
path := filepath.Join(dir, safeName.ReplaceAllString(p.Name, "-")+".json")
|
|
return path, os.WriteFile(path, append(b, '\n'), 0o644)
|
|
}
|
|
|
|
// DeleteUser entfernt ein User-Preset. Built-ins können nicht gelöscht werden.
|
|
func DeleteUser(name string) error {
|
|
dir, err := Dir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
path := filepath.Join(dir, safeName.ReplaceAllString(name, "-")+".json")
|
|
if err := os.Remove(path); err != nil {
|
|
return fmt.Errorf("nur User-Presets sind löschbar: %w", err)
|
|
}
|
|
return nil
|
|
}
|