update auf public download
All checks were successful
release-tag / release-image (push) Successful in 1m40s
All checks were successful
release-tag / release-image (push) Successful in 1m40s
This commit is contained in:
@@ -62,11 +62,13 @@ func loadConfig() AppConfig {
|
||||
}
|
||||
|
||||
return AppConfig{
|
||||
HTTPAddr: httpAddr,
|
||||
APIKey: apiKey,
|
||||
AdminUser: adminUser,
|
||||
AdminPass: adminPass,
|
||||
Mesh: m,
|
||||
HTTPAddr: httpAddr,
|
||||
APIKey: apiKey,
|
||||
AdminUser: adminUser,
|
||||
AdminPass: adminPass,
|
||||
Mesh: m,
|
||||
PublicDownloads: parseBoolEnv("PUBLIC_DOWNLOADS", false),
|
||||
PublicPath: getenvDefault("PUBLIC_DOWNLOAD_PATH", "/dl"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +139,9 @@ type AppConfig struct {
|
||||
AdminUser string
|
||||
AdminPass string
|
||||
Mesh mesh.Config
|
||||
|
||||
PublicDownloads bool // ENV: PUBLIC_DOWNLOADS (default false)
|
||||
PublicPath string // ENV: PUBLIC_DOWNLOAD_PATH (default "/dl")
|
||||
}
|
||||
|
||||
/*** Middleware ***/
|
||||
@@ -455,6 +460,10 @@ func main() {
|
||||
apiFiles(apiMux, st, blobs, mnode)
|
||||
root.Handle("/api/", authMiddleware(cfg.APIKey, apiMux))
|
||||
|
||||
if cfg.PublicDownloads {
|
||||
registerPublicDownloads(root, blobs, mnode, cfg.PublicPath)
|
||||
}
|
||||
|
||||
// Admin-UI (optional BasicAuth via ADMIN_USER/ADMIN_PASS)
|
||||
adminRoot := http.NewServeMux()
|
||||
admin.Register(adminRoot, admin.Deps{Store: st, Mesh: mnode, Blob: blobs})
|
||||
@@ -502,3 +511,78 @@ func main() {
|
||||
_ = mnode.Close(ctx)
|
||||
log.Println("shutdown complete")
|
||||
}
|
||||
|
||||
// Public: GET {base}/{id}
|
||||
// Beispiel: /dl/1
|
||||
func registerPublicDownloads(mux *http.ServeMux, blobs blobfs.Store, meshNode *mesh.Node, base string) {
|
||||
if !strings.HasPrefix(base, "/") {
|
||||
base = "/" + base
|
||||
}
|
||||
mux.HandleFunc(base+"/", func(w http.ResponseWriter, r *http.Request) {
|
||||
idStr := strings.TrimPrefix(r.URL.Path, base+"/")
|
||||
if idStr == "" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// 1) lokal versuchen
|
||||
if rc, meta, err := blobs.Open(r.Context(), id); err == nil {
|
||||
defer rc.Close()
|
||||
serveBlob(w, r, rc, meta)
|
||||
return
|
||||
}
|
||||
|
||||
// 2) aus dem Mesh ziehen (signiert) und lokal cachen
|
||||
rrc, name, _, _, err := meshNode.FetchBlobAny(r.Context(), id)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
defer rrc.Close()
|
||||
if _, err := blobs.Save(r.Context(), id, name, rrc); err != nil {
|
||||
http.Error(w, "cache failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 3) erneut lokal öffnen (saubere Meta/Größe/CT)
|
||||
lrc, meta, err := blobs.Open(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, "open failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer lrc.Close()
|
||||
serveBlob(w, r, lrc, meta)
|
||||
})
|
||||
}
|
||||
|
||||
// Hilfsfunktion: setzt sinnvolle Header und streamt die Datei
|
||||
func serveBlob(w http.ResponseWriter, r *http.Request, rc io.ReadSeeker, meta blobfs.Meta) {
|
||||
// Caching (einfach): ETag = SHA256
|
||||
if meta.SHA256 != "" {
|
||||
etag := `W/"` + meta.SHA256 + `"`
|
||||
if inm := r.Header.Get("If-None-Match"); inm == etag {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
w.Header().Set("ETag", etag)
|
||||
}
|
||||
|
||||
// Standard-Header
|
||||
if meta.ContentType == "" {
|
||||
meta.ContentType = "application/octet-stream"
|
||||
}
|
||||
w.Header().Set("Content-Type", meta.ContentType)
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(meta.Size, 10))
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, meta.Name))
|
||||
w.Header().Set("Access-Control-Expose-Headers", "Content-Disposition") // für Browser-JS
|
||||
w.Header().Set("X-Robots-Tag", "noindex")
|
||||
|
||||
// (Optional) einfache Range-Unterstützung weglassen, um’s schlank zu halten
|
||||
// Stream
|
||||
_, _ = io.Copy(w, rc)
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ func Register(mux *http.ServeMux, d Deps) {
|
||||
_ = d.Mesh.SyncNow(r.Context()) // prompt push (best effort)
|
||||
}
|
||||
// Nach Aktion Items partial zurückgeben (HTMX swap)
|
||||
http.Redirect(w, r, "/admin/items", http.StatusSeeOther)
|
||||
http.Redirect(w, r, "/admin", http.StatusSeeOther)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/admin/items/rename", func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -213,7 +213,7 @@ func Register(mux *http.ServeMux, d Deps) {
|
||||
_, _ = d.Store.Rename(r.Context(), filesvc.ID(id), newName)
|
||||
_ = d.Mesh.SyncNow(r.Context())
|
||||
}
|
||||
http.Redirect(w, r, "/admin/items", http.StatusSeeOther)
|
||||
http.Redirect(w, r, "/admin", http.StatusSeeOther) //hier test nicht mehr /admin/items
|
||||
})
|
||||
|
||||
mux.HandleFunc("/admin/items/delete", func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -226,7 +226,7 @@ func Register(mux *http.ServeMux, d Deps) {
|
||||
_ = d.Blob.Delete(r.Context(), int64(id))
|
||||
_ = d.Mesh.SyncNow(r.Context())
|
||||
}
|
||||
http.Redirect(w, r, "/admin/items", http.StatusSeeOther)
|
||||
http.Redirect(w, r, "/admin", http.StatusSeeOther)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/admin/mesh/syncnow", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user