package main import ( "encoding/json" "fmt" "html/template" "io" "net/http" "os" "os/exec" "os/signal" "path/filepath" "strconv" "strings" "syscall" "time" "git.send.nrw/sendnrw/b1tsblog/internal/article" ) var TickCatalog []TickEntry func SaveTickCatalog(filename string) error { file, err := os.Create(filename) if err != nil { return err } defer file.Close() encoder := json.NewEncoder(file) encoder.SetIndent("", " ") return encoder.Encode(TickCatalog) } func LoadTickCatalog(filename string) error { file, err := os.Open(filename) if err != nil { return err } defer file.Close() return json.NewDecoder(file).Decode(&TickCatalog) } func IncTick(xSlug string) error { for a, b := range TickCatalog { if b.Slug == xSlug { TickCatalog[a].Count = TickCatalog[a].Count + 1 return nil } } newEntry := TickEntry{Slug: xSlug, Count: 1} TickCatalog = append(TickCatalog, newEntry) return fmt.Errorf("") } func getTick(xSlug string) string { for _, b := range TickCatalog { if b.Slug == xSlug { var n int64 = b.Count return strconv.FormatInt(n, 10) } } return "0" } type TickEntry struct { Slug string `json:"slug"` Count int64 `json:"count"` } func getenv(k, d string) string { if v := os.Getenv(k); v != "" { return v } return d } func enabled(k string, def bool) bool { b, err := strconv.ParseBool(strings.ToLower(os.Getenv(k))) if err != nil { return def } return b } func cacheControl(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") next.ServeHTTP(w, r) }) } var ( tplList *template.Template tplArticle *template.Template ) var Xarticles []article.Article func main() { // Signal-Kanal einrichten stop := make(chan os.Signal, 1) signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) // Goroutine, die auf Signale wartet go func() { <-stop fmt.Println("Stop Sign...") prepareExit() os.Exit(0) }() // --- Verzeichnisse konfigurierbar machen ------------------------- contentDir := getenv("BLOG_CONTENT_DIR", "/content") staticDir := getenv("BLOG_STATIC_DIR", "/app/internal/web/static") pagesDir := getenv("BLOG_PAGES_DIR", "/pages") templatesDir := getenv("BLOG_TEMPLATES_DIR", "/templates") ticksDir := getenv("BLOG_TICKS_DIR", "/ticks") gitEnable := enabled("GIT_ENABLE", false) gitRepo := getenv("GIT_REPO", "null") gitBranch := getenv("GIT_BRANCH", "main") gitDir := getenv("GIT_DIR", "/git-temp") gitInterval := getenv("GIT_INTERVAL", "10") TickCatalog = nil if err := LoadTickCatalog(ticksDir + "/ticks.json"); err != nil { fmt.Println("Fehler beim Laden:", err) } fmt.Println("Geladener Katalog:", TickCatalog) funcs := template.FuncMap{ "now": time.Now, // jetzt‑Zeit bereitstellen } // Basislayout zuerst parsen layout := template.Must(template.New("base").Funcs(funcs).ParseFiles(templatesDir + "/base.html")) // LIST‑Seite: base + list.html tplList = template.Must(layout.Clone()) template.Must(tplList.Funcs(funcs).ParseFiles(templatesDir + "/list.html")) // ARTICLE‑Instanz tplArticle = template.Must(layout.Clone()) template.Must(tplArticle.Funcs(funcs).ParseFiles(templatesDir + "/article.html")) tplPage := template.Must(layout.Clone()) template.Must(tplPage.ParseFiles(templatesDir + "/page.html")) mux := http.NewServeMux() articles, err := article.LoadDir(contentDir) if err != nil { fmt.Println(err) } Xarticles = articles staticPages, err := article.LoadStatic(pagesDir) if err != nil { fmt.Println(err) } else { fmt.Println(staticPages) } // Handler für / mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } /* */ for a, b := range Xarticles { Xarticles[a].Counter = getTick(b.Slug) } /* */ if err := tplList.ExecuteTemplate(w, "layout", article.ListPage{ Title: "Startseite", Description: "Alle Artikel im Überblick", Articles: Xarticles, }); err != nil { http.Error(w, err.Error(), 500) } }) mux.HandleFunc("/post/", func(w http.ResponseWriter, r *http.Request) { slug := strings.TrimPrefix(r.URL.Path, "/post/") for _, a := range Xarticles { if a.Slug == slug { IncTick(slug) t := getTick(slug) a.Counter = t if err := tplArticle.ExecuteTemplate(w, "layout", a); err != nil { http.Error(w, err.Error(), 500) } return } } http.NotFound(w, r) }) mux.HandleFunc("/page/", func(w http.ResponseWriter, r *http.Request) { slug := strings.TrimPrefix(r.URL.Path, "/page/") p, ok := staticPages[slug] if !ok { http.NotFound(w, r) return } // "layout" kommt aus deinem Template‑Pool (list/article nutzen es ja auch) if err := tplPage.ExecuteTemplate(w, "page", p); err != nil { http.Error(w, err.Error(), 500) } }) mux.Handle("/static/", cacheControl(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))))) if gitEnable { xMinute, _ := strconv.Atoi(gitInterval) xDuration := time.Duration(xMinute) * time.Minute go startAutoClone(gitRepo, gitBranch, gitDir, xDuration) } StopServer(http.ListenAndServe(":8080", mux)) } func prepareExit() { fmt.Println("~", "Running exit tasks...") if err := SaveTickCatalog(getenv("BLOG_TICKS_DIR", "/ticks") + "/ticks.json"); err != nil { fmt.Println("Fehler beim Speichern:", err) } fmt.Println("Geladener Katalog:", TickCatalog) fmt.Println("~", "Exit completed.") } func StopServer(e error) { fmt.Println("~", "Stopping server...") prepareExit() fmt.Println("~", "Server stopped!") } func cloneRepo(repoURL, branch, dir string) { fmt.Printf("Starte Klonvorgang für Branch '%s'...\n", branch) // Verzeichnis löschen if err := os.RemoveAll(dir); err != nil { fmt.Println("Fehler beim Löschen des Verzeichnisses:", err) return } // Git-Clone mit Branch cmd := exec.Command("git", "clone", "--branch", branch, "--single-branch", repoURL, dir) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Println("Fehler beim Klonen:", err) } else { fmt.Println("Repo erfolgreich geklont.") } contentDir := getenv("BLOG_CONTENT_DIR", "/content") err := os.RemoveAll("/content") if err != nil { fmt.Println(err, "/content") } err = os.RemoveAll("/static") if err != nil { fmt.Println(err, "/static") } err = os.RemoveAll("/pages") if err != nil { fmt.Println(err, "/pages") } err = os.RemoveAll("/templates") if err != nil { fmt.Println(err, "/templates") } if err := os.MkdirAll("/content", 0755); err != nil { fmt.Println("Fehler beim Erstellen des Zielordners:", err) return } if err := os.MkdirAll("/static", 0755); err != nil { fmt.Println("Fehler beim Erstellen des Zielordners:", err) return } if err := os.MkdirAll("/pages", 0755); err != nil { fmt.Println("Fehler beim Erstellen des Zielordners:", err) return } if err := os.MkdirAll("/templates", 0755); err != nil { fmt.Println("Fehler beim Erstellen des Zielordners:", err) return } if err := copyDirContents("/git-temp/articles", "/content"); err != nil { fmt.Println("Fehler beim Kopieren:", err) } else { fmt.Println("Kopieren abgeschlossen.") } if err := copyDirContents("/git-temp/static", "/static"); err != nil { fmt.Println("Fehler beim Kopieren:", err) } else { fmt.Println("Kopieren abgeschlossen.") } if err := copyDirContents("/git-temp/pages", "/pages"); err != nil { fmt.Println("Fehler beim Kopieren:", err) } else { fmt.Println("Kopieren abgeschlossen.") } if err := copyDirContents("/git-temp/templates", "/templates"); err != nil { fmt.Println("Fehler beim Kopieren:", err) } else { fmt.Println("Kopieren abgeschlossen.") } articles, err := article.LoadDir(contentDir) if err != nil { fmt.Println(err) } Xarticles = articles } func copyDirContents(srcDir, destDir string) error { entries, err := os.ReadDir(srcDir) if err != nil { return fmt.Errorf("Fehler beim Lesen von %s: %w", srcDir, err) } for _, entry := range entries { srcPath := filepath.Join(srcDir, entry.Name()) destPath := filepath.Join(destDir, entry.Name()) info, err := entry.Info() if err != nil { return err } if info.IsDir() { if err := os.MkdirAll(destPath, info.Mode()); err != nil { return fmt.Errorf("Fehler beim Erstellen von Ordner %s: %w", destPath, err) } // rekursiv kopieren if err := copyDirContents(srcPath, destPath); err != nil { return err } } else { if err := copyFile(srcPath, destPath, info); err != nil { return err } } } return nil } func copyFile(src, dest string, info os.FileInfo) error { in, err := os.Open(src) if err != nil { return err } defer in.Close() out, err := os.Create(dest) if err != nil { return err } defer out.Close() if _, err := io.Copy(out, in); err != nil { return err } return os.Chmod(dest, info.Mode()) } func startAutoClone(repoURL, branch, dir string, interval time.Duration) { go cloneRepo(repoURL, branch, dir) // sofortiger Start ticker := time.NewTicker(interval) defer ticker.Stop() for range ticker.C { go cloneRepo(repoURL, branch, dir) } }