Debug ohne Embed
All checks were successful
release-tag / release-image (push) Successful in 1m56s

This commit is contained in:
2025-09-21 17:55:34 +02:00
parent d24eeb1c58
commit 4f8a2fd178
13 changed files with 47 additions and 47 deletions

View File

@@ -10,6 +10,7 @@ import (
"net/http/httputil"
"net/url"
"os"
"path/filepath"
"strings"
"time"
@@ -18,7 +19,6 @@ import (
"github.com/goccy/go-json"
"git.send.nrw/sendnrw/nginx-stream-server/internal/mtx"
"git.send.nrw/sendnrw/nginx-stream-server/internal/ui"
)
var (
@@ -28,6 +28,11 @@ var (
streamsCSV = os.Getenv("STREAMS")
basicUser = os.Getenv("BASIC_AUTH_USER")
basicPass = os.Getenv("BASIC_AUTH_PASS")
// Root für Webassets (per ENV überschreibbar)
webRoot = env("WEB_ROOT", "web")
// Templates werden beim Start geladen
tpls *template.Template
)
func init() {
@@ -45,10 +50,21 @@ func env(k, def string) string {
return def
}
func mustLoadTemplates() *template.Template {
pattern := filepath.Join(webRoot, "templates", "*.html")
t, err := template.ParseGlob(pattern)
if err != nil {
log.Fatalf("templates laden fehlgeschlagen (%s): %v", pattern, err)
}
return t
}
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
tpls = mustLoadTemplates()
r := chi.NewRouter()
// Strenge CSP (alles lokal)
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Security-Policy",
@@ -59,7 +75,6 @@ func main() {
"font-src 'self'",
"script-src 'self'",
"connect-src 'self'",
// optional: "media-src 'self'",
}, "; "),
)
w.Header().Set("X-Content-Type-Options", "nosniff")
@@ -69,11 +84,13 @@ func main() {
})
})
// API
r.Group(func(r chi.Router) {
r.Use(httprate.LimitByIP(30, time.Minute))
r.Get("/api/streams", apiStreams)
})
// Optional Basic Auth für Seiten
if basicUser != "" {
creds := basicUser + ":" + basicPass
r.Group(func(p chi.Router) {
@@ -96,18 +113,20 @@ func main() {
r.Get("/{name}", pageStream)
}
// /static → echtes Dateisystem
staticDir := http.Dir(filepath.Join(webRoot, "static"))
r.Handle("/static/*",
http.StripPrefix("/static",
http.FileServer(staticDir),
),
)
// HLS-Reverse-Proxy
up, _ := url.Parse(mtxHLS)
proxy := httputil.NewSingleHostReverseProxy(up)
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, e error) {
http.Error(w, "upstream error", http.StatusBadGateway)
}
r.Handle("/static/*",
http.StripPrefix("/static",
http.FileServer(http.FS(ui.StaticFS)),
),
)
r.Handle("/hls/*", http.StripPrefix("/hls", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "..") {
http.NotFound(w, r)
@@ -116,9 +135,10 @@ func main() {
proxy.ServeHTTP(w, r)
})))
// Health
r.Get("/health", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) })
log.Printf("Dashboard listening on %s (API=%s HLS=%s)\n", listen, mtxAPI, mtxHLS)
log.Printf("Dashboard listening on %s (API=%s HLS=%s, WEB_ROOT=%s)\n", listen, mtxAPI, mtxHLS, webRoot)
if err := http.ListenAndServe(listen, r); err != nil {
log.Fatal(err)
}
@@ -158,22 +178,24 @@ func apiStreams(w http.ResponseWriter, r *http.Request) {
}
func pageIndex(w http.ResponseWriter, r *http.Request) {
b, _ := ui.FS.ReadFile("index.html")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(b)
if err := tpls.ExecuteTemplate(w, "index.html", nil); err != nil {
http.Error(w, err.Error(), 500)
}
}
func pageStream(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
tpl, err := template.ParseFS(ui.FS, "stream.html")
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
tpl.Execute(w, map[string]any{"Name": name, "JSONName": fmt.Sprintf("%q", name)})
if err := tpls.ExecuteTemplate(w, "stream.html", map[string]any{
"Name": name,
"JSONName": fmt.Sprintf("%q", name),
}); err != nil {
http.Error(w, err.Error(), 500)
}
}
// Basic helper bleibt, falls du Basic Auth nutzt
func basic(creds string) string {
const tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
b := []byte(creds)