feat: auto-configure FileBrowser sidebar with per-drive sources
Generate config.yaml with a separate source per registered storage path. Each source uses the drive's label as its display name, making it appear automatically in FileBrowser's sidebar. The config.yaml is bind-mounted into the container (read-only) alongside the data volume. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1260,10 +1260,11 @@ func (s *Server) settingsStorageLabelHandler(w http.ResponseWriter, r *http.Requ
|
||||
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.
|
||||
// syncFileBrowserMounts regenerates FileBrowser's docker-compose.yml and config.yaml
|
||||
// with volume mounts and sources for all registered storage paths, then recreates the container.
|
||||
func (s *Server) syncFileBrowserMounts() {
|
||||
composePath := "/opt/docker/stacks/filebrowser/docker-compose.yml"
|
||||
stackDir := "/opt/docker/stacks/filebrowser"
|
||||
composePath := stackDir + "/docker-compose.yml"
|
||||
|
||||
// Check if FileBrowser stack exists
|
||||
if _, err := os.Stat(composePath); os.IsNotExist(err) {
|
||||
@@ -1275,7 +1276,7 @@ func (s *Server) syncFileBrowserMounts() {
|
||||
paths := s.settings.GetStoragePaths()
|
||||
|
||||
// Read domain from .env
|
||||
envPath := "/opt/docker/stacks/filebrowser/.env"
|
||||
envPath := stackDir + "/.env"
|
||||
domain := ""
|
||||
if data, err := os.ReadFile(envPath); err == nil {
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
@@ -1298,10 +1299,16 @@ func (s *Server) syncFileBrowserMounts() {
|
||||
storageMounts = append(storageMounts, line)
|
||||
}
|
||||
|
||||
// Generate compose from template
|
||||
compose := generateFileBrowserCompose(domain, storageMounts)
|
||||
// Generate and write config.yaml (sources + sidebar entries per drive)
|
||||
configPath := stackDir + "/config.yaml"
|
||||
fbConfig := generateFileBrowserConfig(paths)
|
||||
if err := os.WriteFile(configPath, []byte(fbConfig), 0644); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to write FileBrowser config: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Write compose
|
||||
// Generate and write compose (includes config.yaml mount)
|
||||
compose := generateFileBrowserCompose(domain, storageMounts)
|
||||
if err := os.WriteFile(composePath, []byte(compose), 0644); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to write FileBrowser compose: %v", err)
|
||||
return
|
||||
@@ -1311,11 +1318,11 @@ func (s *Server) syncFileBrowserMounts() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, "docker", "compose", "up", "-d", "--remove-orphans")
|
||||
cmd.Dir = filepath.Dir(composePath)
|
||||
cmd.Dir = stackDir
|
||||
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))
|
||||
s.logger.Printf("[INFO] FileBrowser mounts synced — %d storage path(s), config updated", len(paths))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1341,7 +1348,8 @@ services:
|
||||
environment:
|
||||
- TZ=Europe/Budapest
|
||||
volumes:
|
||||
- filebrowser_data:/home/filebrowser/data%s
|
||||
- filebrowser_data:/home/filebrowser/data
|
||||
- ./config.yaml:/home/filebrowser/data/config.yaml:ro%s
|
||||
networks:
|
||||
- traefik-public
|
||||
deploy:
|
||||
@@ -1370,3 +1378,61 @@ networks:
|
||||
external: true
|
||||
`, domain, storageSection, domain)
|
||||
}
|
||||
|
||||
// generateFileBrowserConfig returns a FileBrowser Quantum config.yaml with
|
||||
// a separate source per registered storage path. Each source appears as a
|
||||
// named sidebar entry in the FileBrowser UI.
|
||||
func generateFileBrowserConfig(paths []settings.StoragePath) string {
|
||||
var sources string
|
||||
if len(paths) == 0 {
|
||||
sources = ` - path: "/srv"
|
||||
`
|
||||
} else {
|
||||
for _, sp := range paths {
|
||||
mountName := filepath.Base(sp.Path)
|
||||
label := sp.Label
|
||||
if label == "" {
|
||||
label = mountName
|
||||
}
|
||||
sources += fmt.Sprintf(" - path: \"/srv/%s\"\n name: %q\n config:\n defaultEnabled: true\n", mountName, label)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`# FileBrowser Quantum — managed by felhom-controller
|
||||
# WARNING: This file is auto-generated. Manual edits will be overwritten.
|
||||
|
||||
server:
|
||||
port: 80
|
||||
baseURL: "/"
|
||||
logging:
|
||||
- levels: "info|warning|error"
|
||||
sources:
|
||||
%suserDefaults:
|
||||
stickySidebar: true
|
||||
darkMode: true
|
||||
viewMode: "normal"
|
||||
showHidden: false
|
||||
dateFormat: false
|
||||
gallerySize: 3
|
||||
themeColor: "var(--blue)"
|
||||
preview:
|
||||
disableHideSidebar: false
|
||||
highQuality: true
|
||||
image: true
|
||||
video: true
|
||||
motionVideoPreview: true
|
||||
office: true
|
||||
popup: true
|
||||
autoplayMedia: true
|
||||
folder: true
|
||||
permissions:
|
||||
api: false
|
||||
admin: false
|
||||
modify: false
|
||||
share: false
|
||||
realtime: false
|
||||
delete: false
|
||||
create: false
|
||||
download: true
|
||||
`, sources)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user