From 988a9702c001c85983ba21955dfd6900ff503a4b Mon Sep 17 00:00:00 2001 From: Mark Ferry Date: Thu, 11 Sep 2025 16:18:05 +0100 Subject: [PATCH 1/5] fix #201: add --config flag --- README.md | 6 ++++-- cmd/daemon/main.go | 9 +++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ebee505..cdd4985 100644 --- a/README.md +++ b/README.md @@ -62,12 +62,14 @@ Details about cross-compiling go-librespot are described [here](/CROSS_COMPILE.m The default directory for configuration files is `~/.config/go-librespot`. On macOS devices, this is `~/Library/Application Support/go-librespot`. You can change this directory with the -`-config_dir` flag. The configuration directory contains: +`--config_dir` flag. The configuration directory contains: - `config.yml`: The main configuration (does not exist by default) - `state.json`: The player state and credentials - `lockfile`: A lockfile to prevent running multiple instances on the same configuration +You may also specify a custom config file with the `--config` flag. + The full configuration schema is available [here](/config_schema.json), only the main options are detailed below. ### Zeroconf mode @@ -187,4 +189,4 @@ or using Go: ```shell go generate ./... -``` \ No newline at end of file +``` diff --git a/cmd/daemon/main.go b/cmd/daemon/main.go index 2f7ba53..2e4849c 100644 --- a/cmd/daemon/main.go +++ b/cmd/daemon/main.go @@ -369,6 +369,7 @@ func (app *App) withAppPlayer(ctx context.Context, appPlayerFunc func(context.Co type Config struct { ConfigDir string `koanf:"config_dir"` + ConfigPath string `koanf:"config"` // We need to keep this object around, otherwise it gets GC'd and the // finalizer will run, probably closing the lock. @@ -436,6 +437,10 @@ func loadConfig(cfg *Config) error { } defaultConfigDir := filepath.Join(userConfigDir, "go-librespot") f.StringVar(&cfg.ConfigDir, "config_dir", defaultConfigDir, "the configuration directory") + + defaultConfigPath := filepath.Join(defaultConfigDir, "config.yaml") + f.StringVar(&cfg.ConfigPath, "config", defaultConfigPath, "the configuration file") + err = f.Parse(os.Args[1:]) if err != nil { return err @@ -481,10 +486,10 @@ func loadConfig(cfg *Config) error { // load file configuration (if available) var configPath string - if _, err := os.Stat(filepath.Join(cfg.ConfigDir, "config.yaml")); os.IsNotExist(err) { + if _, err := os.Stat(cfg.ConfigPath); os.IsNotExist(err) { configPath = filepath.Join(cfg.ConfigDir, "config.yml") } else { - configPath = filepath.Join(cfg.ConfigDir, "config.yaml") + configPath = cfg.ConfigPath } if err := k.Load(file.Provider(configPath), yaml.Parser()); err != nil { From e51e43e6178c54cce4a28dbd045faa7f4434155d Mon Sep 17 00:00:00 2001 From: Mark Ferry Date: Thu, 11 Sep 2025 17:14:27 +0100 Subject: [PATCH 2/5] add --cache flag Default state dir moves from os.UserConfigDir to os.UserCacheDir --- README.md | 17 ++++++++++------- cmd/daemon/main.go | 34 +++++++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index cdd4985..6315353 100644 --- a/README.md +++ b/README.md @@ -60,15 +60,18 @@ Details about cross-compiling go-librespot are described [here](/CROSS_COMPILE.m ## Configuration -The default directory for configuration files is `~/.config/go-librespot`. On macOS devices, this is -`~/Library/Application Support/go-librespot`. You can change this directory with the -`--config_dir` flag. The configuration directory contains: +The main configuration files is `~/.config/go-librespot/config.yaml`. On macOS devices, this is +`~/Library/Application Support/go-librespot/config.yaml`. It does not exist by default. -- `config.yml`: The main configuration (does not exist by default) -- `state.json`: The player state and credentials -- `lockfile`: A lockfile to prevent running multiple instances on the same configuration +You can change this path with the `--config` flag. + +Cache files are stored in `~/.cache/go-librespot` (`~/Library/Caches/go-librespot` on macOS). -You may also specify a custom config file with the `--config` flag. +You can change this directory with the `--cache` flag. + +The cache directory contains: +- `state.json`: The player state and credentials +- `lockfile`: A lockfile to prevent multiple instances using the same state The full configuration schema is available [here](/config_schema.json), only the main options are detailed below. diff --git a/cmd/daemon/main.go b/cmd/daemon/main.go index 2e4849c..beb47c6 100644 --- a/cmd/daemon/main.go +++ b/cmd/daemon/main.go @@ -79,7 +79,7 @@ func NewApp(cfg *Config) (app *App, err error) { } app.state.SetLogger(app.log) - if err := app.state.Read(cfg.ConfigDir); err != nil { + if err := app.state.Read(cfg.CacheDir); err != nil { return nil, err } @@ -368,7 +368,7 @@ func (app *App) withAppPlayer(ctx context.Context, appPlayerFunc func(context.Co } type Config struct { - ConfigDir string `koanf:"config_dir"` + CacheDir string `koanf:"cache"` ConfigPath string `koanf:"config"` // We need to keep this object around, otherwise it gets GC'd and the @@ -425,8 +425,19 @@ type Config struct { } `koanf:"credentials"` } +// backwards compatibility for config_dir flag +func aliasNormalizeFunc(f *flag.FlagSet, name string) flag.NormalizedName { + switch name { + case "config_dir": + name = "cache" + break + } + return flag.NormalizedName(name) +} + func loadConfig(cfg *Config) error { f := flag.NewFlagSet("config", flag.ContinueOnError) + f.SetNormalizeFunc(aliasNormalizeFunc) f.Usage = func() { fmt.Println(f.FlagUsages()) os.Exit(0) @@ -435,26 +446,30 @@ func loadConfig(cfg *Config) error { if err != nil { return err } - defaultConfigDir := filepath.Join(userConfigDir, "go-librespot") - f.StringVar(&cfg.ConfigDir, "config_dir", defaultConfigDir, "the configuration directory") - - defaultConfigPath := filepath.Join(defaultConfigDir, "config.yaml") + defaultConfigPath := filepath.Join(userConfigDir, "go-librespot", "config.yaml") f.StringVar(&cfg.ConfigPath, "config", defaultConfigPath, "the configuration file") + userCacheDir, err := os.UserCacheDir() + if err != nil { + return err + } + defaultCachePath := filepath.Join(userCacheDir, "go-librespot") + f.StringVar(&cfg.CacheDir, "cache", defaultCachePath, "the cache directory") + err = f.Parse(os.Args[1:]) if err != nil { return err } // Make config directory if needed. - err = os.MkdirAll(cfg.ConfigDir, 0o700) + err = os.MkdirAll(cfg.CacheDir, 0o700) if err != nil { return fmt.Errorf("failed creating config directory: %w", err) } // Lock the config directory (to ensure multiple instances won't clobber // each others state). - lockFilePath := filepath.Join(cfg.ConfigDir, "lockfile") + lockFilePath := filepath.Join(cfg.CacheDir, "lockfile") cfg.configLock = flock.New(lockFilePath) if locked, err := cfg.configLock.TryLock(); err != nil { return fmt.Errorf("could not lock config directory: %w", err) @@ -487,7 +502,8 @@ func loadConfig(cfg *Config) error { // load file configuration (if available) var configPath string if _, err := os.Stat(cfg.ConfigPath); os.IsNotExist(err) { - configPath = filepath.Join(cfg.ConfigDir, "config.yml") + // postel: allow .yml in place of .yaml + configPath = strings.TrimSuffix(cfg.ConfigPath, filepath.Ext(cfg.ConfigPath)) + ".yml" } else { configPath = cfg.ConfigPath } From a7f6fcbd8f46033f4ecbb3eb785ee98e631b4284 Mon Sep 17 00:00:00 2001 From: Mark Ferry Date: Thu, 11 Sep 2025 17:55:30 +0100 Subject: [PATCH 3/5] rename to cacheLock --- cmd/daemon/main.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/daemon/main.go b/cmd/daemon/main.go index beb47c6..a156d6b 100644 --- a/cmd/daemon/main.go +++ b/cmd/daemon/main.go @@ -373,7 +373,7 @@ type Config struct { // We need to keep this object around, otherwise it gets GC'd and the // finalizer will run, probably closing the lock. - configLock *flock.Flock + cacheLock *flock.Flock LogLevel log.Level `koanf:"log_level"` LogDisableTimestamp bool `koanf:"log_disable_timestamp"` @@ -461,18 +461,18 @@ func loadConfig(cfg *Config) error { return err } - // Make config directory if needed. + // Make cache directory if needed. err = os.MkdirAll(cfg.CacheDir, 0o700) if err != nil { - return fmt.Errorf("failed creating config directory: %w", err) + return fmt.Errorf("failed creating cache directory: %w", err) } - // Lock the config directory (to ensure multiple instances won't clobber + // Lock the cache directory (to ensure multiple instances won't clobber // each others state). lockFilePath := filepath.Join(cfg.CacheDir, "lockfile") - cfg.configLock = flock.New(lockFilePath) - if locked, err := cfg.configLock.TryLock(); err != nil { - return fmt.Errorf("could not lock config directory: %w", err) + cfg.cacheLock = flock.New(lockFilePath) + if locked, err := cfg.cacheLock.TryLock(); err != nil { + return fmt.Errorf("could not lock cache directory: %w", err) } else if !locked { // Lock already taken! Looks like go-librespot is already running. return fmt.Errorf("%w (lockfile: %s)", errAlreadyRunning, lockFilePath) From 439ede4e4d74bed6dc2941359bb193173a9bea69 Mon Sep 17 00:00:00 2001 From: Mark Ferry Date: Thu, 11 Sep 2025 17:51:08 +0100 Subject: [PATCH 4/5] update Docker files --- Dockerfile | 2 +- docker-compose.pulse.yml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index d95f8b6..367a8be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,4 +16,4 @@ RUN apk -U --no-cache add libpulse avahi libgcc gcompat alsa-lib COPY --from=build /src/daemon /usr/bin/go-librespot -CMD ["/usr/bin/go-librespot", "--config_dir", "/config"] \ No newline at end of file +CMD ["/usr/bin/go-librespot", "--cache", "/cache", "--config", "/config/config.yaml"] diff --git a/docker-compose.pulse.yml b/docker-compose.pulse.yml index 24ad4be..0dba643 100644 --- a/docker-compose.pulse.yml +++ b/docker-compose.pulse.yml @@ -6,8 +6,9 @@ services: userns_mode: keep-id volumes: - ~/.config/go-librespot:/config + - ~/.cache/go-librespot:/cache - ~/.config/pulse/cookie:/pulse_cookie:ro - /run/user/1000/pulse/native:/pulse_native # Replace 1000 with your UID environment: PULSE_SERVER: "unix:/pulse_native" - PULSE_COOKIE: "/pulse_cookie" \ No newline at end of file + PULSE_COOKIE: "/pulse_cookie" From 96f73c79e097900f036158a19ad4155212f5b4ca Mon Sep 17 00:00:00 2001 From: Mark Ferry Date: Thu, 11 Sep 2025 18:30:20 +0100 Subject: [PATCH 5/5] gofmt --- cmd/daemon/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/daemon/main.go b/cmd/daemon/main.go index a156d6b..dcff675 100644 --- a/cmd/daemon/main.go +++ b/cmd/daemon/main.go @@ -368,7 +368,7 @@ func (app *App) withAppPlayer(ctx context.Context, appPlayerFunc func(context.Co } type Config struct { - CacheDir string `koanf:"cache"` + CacheDir string `koanf:"cache"` ConfigPath string `koanf:"config"` // We need to keep this object around, otherwise it gets GC'd and the