diff --git a/Dockerfile b/Dockerfile index fc98bd9..12bb5ba 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/README.md b/README.md index dcdb715..bb4a97d 100644 --- a/README.md +++ b/README.md @@ -60,13 +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) +You can change this path with the `--config` flag. + +Cache files are stored in `~/.cache/go-librespot` (`~/Library/Caches/go-librespot` on macOS). + +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 running multiple instances on the same configuration +- `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. @@ -208,4 +213,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 214accf..660e751 100644 --- a/cmd/daemon/main.go +++ b/cmd/daemon/main.go @@ -80,7 +80,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 } @@ -380,11 +380,12 @@ 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 // 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"` @@ -439,8 +440,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) @@ -449,25 +461,33 @@ 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(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) + // 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.ConfigDir, "lockfile") - cfg.configLock = flock.New(lockFilePath) - if locked, err := cfg.configLock.TryLock(); err != nil { - return fmt.Errorf("could not lock config directory: %w", err) + lockFilePath := filepath.Join(cfg.CacheDir, "lockfile") + 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) @@ -498,10 +518,11 @@ 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) { - configPath = filepath.Join(cfg.ConfigDir, "config.yml") + if _, err := os.Stat(cfg.ConfigPath); os.IsNotExist(err) { + // postel: allow .yml in place of .yaml + configPath = strings.TrimSuffix(cfg.ConfigPath, filepath.Ext(cfg.ConfigPath)) + ".yml" } else { - configPath = filepath.Join(cfg.ConfigDir, "config.yaml") + configPath = cfg.ConfigPath } if err := k.Load(file.Provider(configPath), yaml.Parser()); err != nil { 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"