diff --git a/controller/internal/web/handlers.go b/controller/internal/web/handlers.go index 42114b4..3aac977 100644 --- a/controller/internal/web/handlers.go +++ b/controller/internal/web/handlers.go @@ -5,6 +5,7 @@ import ( "net/http" "net/url" "os" + "os/exec" "path/filepath" "strings" "time" @@ -795,6 +796,8 @@ func (s *Server) settingsStorageRemoveHandler(w http.ResponseWriter, r *http.Req } s.logger.Printf("[INFO] Storage path removed: %s", path) + // Sync FileBrowser mounts after storage path removal + go s.syncFileBrowserMounts() http.Redirect(w, r, "/settings?storage_msg=success&storage_detail="+url.QueryEscape("Adattároló eltávolítva: "+path), http.StatusFound) } @@ -846,3 +849,112 @@ func (s *Server) settingsStorageLabelHandler(w http.ResponseWriter, r *http.Requ s.logger.Printf("[INFO] Storage label updated: %s → %q", path, label) http.Redirect(w, r, "/settings?storage_msg=success&storage_detail="+url.QueryEscape("Megnevezés módosítva: "+label), http.StatusFound) } + +// syncFileBrowserMounts regenerates FileBrowser's docker-compose.yml +// with volume mounts for all registered storage paths, then recreates the container. +func (s *Server) syncFileBrowserMounts() { + composePath := "/opt/docker/stacks/filebrowser/docker-compose.yml" + + // Check if FileBrowser stack exists + if _, err := os.Stat(composePath); os.IsNotExist(err) { + s.logger.Printf("[WARN] FileBrowser stack not found at %s — skipping mount sync", composePath) + return + } + + // Get all active storage paths + paths := s.settings.GetStoragePaths() + + // Read domain from .env + envPath := "/opt/docker/stacks/filebrowser/.env" + domain := "" + if data, err := os.ReadFile(envPath); err == nil { + for _, line := range strings.Split(string(data), "\n") { + if strings.HasPrefix(line, "DOMAIN=") { + domain = strings.TrimPrefix(line, "DOMAIN=") + break + } + } + } + if domain == "" { + s.logger.Printf("[WARN] Cannot read DOMAIN from FileBrowser .env — skipping mount sync") + return + } + + // Build volume mount lines + var storageMounts []string + for _, sp := range paths { + mountName := filepath.Base(sp.Path) // "/mnt/hdd_1" → "hdd_1" + line := fmt.Sprintf(" - %s:/srv/%s", sp.Path, mountName) + storageMounts = append(storageMounts, line) + } + + // Generate compose from template + compose := generateFileBrowserCompose(domain, storageMounts) + + // Write compose + if err := os.WriteFile(composePath, []byte(compose), 0644); err != nil { + s.logger.Printf("[ERROR] Failed to write FileBrowser compose: %v", err) + return + } + + // Recreate container + cmd := exec.Command("docker", "compose", "up", "-d", "--remove-orphans") + cmd.Dir = filepath.Dir(composePath) + if out, err := cmd.CombinedOutput(); err != nil { + s.logger.Printf("[ERROR] Failed to recreate FileBrowser: %s — %v", string(out), err) + } else { + s.logger.Printf("[INFO] FileBrowser mounts synced — %d storage path(s)", len(paths)) + } +} + +// generateFileBrowserCompose returns a FileBrowser docker-compose.yml string +// with the given domain and storage volume mount lines. +func generateFileBrowserCompose(domain string, storageMounts []string) string { + storageSection := "" + if len(storageMounts) > 0 { + storageSection = "\n # Storage paths (auto-generated by felhom-controller)\n" + + strings.Join(storageMounts, "\n") + } + + return fmt.Sprintf(`# FileBrowser Quantum — Infrastructure file manager +# Domain: files.%s +# Deployed by docker-setup.sh — managed by felhom-controller +# WARNING: Volume mounts are auto-generated. Manual edits will be overwritten. + +services: + filebrowser: + image: gtstef/filebrowser:latest + container_name: filebrowser + restart: unless-stopped + environment: + - TZ=Europe/Budapest + volumes: + - filebrowser_data:/home/filebrowser/data%s + networks: + - traefik-public + deploy: + resources: + limits: + memory: 256M + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:80/"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 15s + labels: + - "traefik.enable=true" + - "traefik.http.routers.filebrowser.rule=Host(`+"`"+`files.%s`+"`"+`)" + - "traefik.http.routers.filebrowser.entrypoints=websecure" + - "traefik.http.routers.filebrowser.tls=true" + - "traefik.http.services.filebrowser.loadbalancer.server.port=80" + - "traefik.docker.network=traefik-public" + +volumes: + filebrowser_data: + +networks: + traefik-public: + external: true +`, domain, storageSection, domain) +} diff --git a/controller/internal/web/storage_handlers.go b/controller/internal/web/storage_handlers.go index d77c6d2..ed3af1a 100644 --- a/controller/internal/web/storage_handlers.go +++ b/controller/internal/web/storage_handlers.go @@ -250,6 +250,8 @@ func (s *Server) storageInitAPIHandler(w http.ResponseWriter, r *http.Request) { s.logger.Printf("[WARN] Failed to register storage path after init: %v", err) } else { s.logger.Printf("[INFO] Storage path registered: %s (%s)", mountPath, label) + // Sync FileBrowser mounts with new storage path + s.syncFileBrowserMounts() } }() diff --git a/controller/internal/web/templates/settings.html b/controller/internal/web/templates/settings.html index 9b53e2e..5fd4ba2 100644 --- a/controller/internal/web/templates/settings.html +++ b/controller/internal/web/templates/settings.html @@ -86,7 +86,7 @@