Update für Persistenz

This commit is contained in:
2025-10-07 20:02:33 +02:00
parent 9f4c081e06
commit b0af9c1444
2 changed files with 85 additions and 11 deletions

86
main.go
View File

@@ -13,10 +13,12 @@ import (
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"syscall"
"time" "time"
) )
@@ -62,6 +64,8 @@ var (
taskIndex = map[string]*Task{} taskIndex = map[string]*Task{}
started time.Time started time.Time
csrfKey []byte csrfKey []byte
cfgPath = "tasks.json" // <- Dateipfad zentral
cfgMu sync.Mutex // <- schützt Speichervorgang
) )
//go:embed web/* //go:embed web/*
@@ -329,6 +333,11 @@ func handleSetInterval(w http.ResponseWriter, r *http.Request) {
return return
} }
t.setInterval(interval, enable) t.setInterval(interval, enable)
// PERSISTENZ
// auch die Werte im cfg.Tasks stecken bereits in t, also genügt:
persistOrLog()
io.WriteString(w, "OK") io.WriteString(w, "OK")
} }
@@ -344,6 +353,10 @@ func handleToggle(w http.ResponseWriter, r *http.Request) {
t.Enabled = enable t.Enabled = enable
t.mutex.Unlock() t.mutex.Unlock()
t.scheduleNext() t.scheduleNext()
// PERSISTENZ
persistOrLog()
io.WriteString(w, "OK") io.WriteString(w, "OK")
} }
@@ -399,16 +412,62 @@ func handleLogs(w http.ResponseWriter, r *http.Request) {
/* ====== Init/Load/Serve ====== */ /* ====== Init/Load/Serve ====== */
func loadConfig() { func saveConfigAtomic() error {
f, err := os.Open("tasks.json") cfgMu.Lock()
defer cfgMu.Unlock()
// Schön formatiert schreiben
b, err := json.MarshalIndent(cfg, "", " ")
if err != nil { if err != nil {
log.Fatalf("tasks.json nicht gefunden: %v", err) return err
}
// In dasselbe Verzeichnis wie cfgPath schreiben (wichtig für Rename-Atomizität)
dir := filepath.Dir(cfgPath)
base := filepath.Base(cfgPath)
tmp := filepath.Join(dir, "."+base+".tmp")
// Temp-Datei erzeugen, schreiben, flushen
f, err := os.OpenFile(tmp, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return err
}
if _, err := f.Write(b); err != nil {
f.Close()
return err
}
if err := f.Sync(); err != nil {
f.Close()
return err
}
if err := f.Close(); err != nil {
return err
}
// Atomisch ersetzen
if err := os.Rename(tmp, cfgPath); err != nil {
return err
}
return nil
}
// bequemer Helfer zum Loggen statt Abbrechen
func persistOrLog() {
if err := saveConfigAtomic(); err != nil {
log.Printf("WARN: Konnte %s nicht speichern: %v", cfgPath, err)
}
}
func loadConfig() {
f, err := os.Open(cfgPath)
if err != nil {
log.Fatalf("%s nicht gefunden: %v", cfgPath, err)
} }
defer f.Close() defer f.Close()
dec := json.NewDecoder(f) dec := json.NewDecoder(f)
dec.DisallowUnknownFields() dec.DisallowUnknownFields()
if err := dec.Decode(&cfg); err != nil { if err := dec.Decode(&cfg); err != nil {
log.Fatalf("tasks.json fehlerhaft: %v", err) log.Fatalf("%s fehlerhaft: %v", cfgPath, err)
} }
if cfg.Listen == "" { if cfg.Listen == "" {
@@ -495,8 +554,25 @@ func main() {
Handler: basicAuth(mux), Handler: basicAuth(mux),
ReadHeaderTimeout: 10 * time.Second, ReadHeaderTimeout: 10 * time.Second,
} }
// GRACEFUL SHUTDOWN + PERSISTENZ
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
go func() {
<-stop
log.Println("Beende... speichere Konfiguration.")
persistOrLog()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = server.Shutdown(ctx)
}()
log.Printf("Weboberfläche: http://%s (Basic Auth aktiv)", addr) log.Printf("Weboberfläche: http://%s (Basic Auth aktiv)", addr)
log.Fatal(server.ListenAndServe()) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
log.Println("Server gestoppt.")
} }
/* ====== Embedded HTML (Tailwind-lite via CDN) ====== */ /* ====== Embedded HTML (Tailwind-lite via CDN) ====== */

View File

@@ -3,21 +3,19 @@
"username": "admin", "username": "admin",
"password": "change-me", "password": "change-me",
"scripts_dir": "C:\\scripts", "scripts_dir": "C:\\scripts",
"csrf_secret": "ersetzenMitEigenemSecret",
"tasks": [ "tasks": [
{ {
"name": "Nacht-Backup", "name": "Nacht-Backup",
"path": "C:\\scripts\\backup.ps1", "path": "C:\\scripts\\backup.ps1",
"interval": "24h", "interval": "24h",
"enabled": true,
"timeout": "2h" "timeout": "2h"
}, },
{ {
"name": "IIS-Log-Rotation", "name": "IIS-Log-Rotation",
"path": "C:\\scripts\\rotate.ps1", "path": "C:\\scripts\\rotate.ps1",
"interval": "0", "interval": "5m",
"enabled": false,
"timeout": "30m" "timeout": "30m"
} }
] ],
} "csrf_secret": "ersetzenMitEigenemSecret"
}