From 6aae522e3007f992a0ea3d4fb7eb82174d392f13 Mon Sep 17 00:00:00 2001 From: Matthias Kurz Date: Thu, 19 Mar 2026 14:41:54 +0100 Subject: [PATCH] feat(secrets): allow overriding keyring service name --- README.md | 8 ++++++ docs/spec.md | 1 + internal/secrets/store.go | 11 +++++++- internal/secrets/store_test.go | 49 +++++++++++++++++++++++++++++++++- 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 13fd0f56..9013e944 100644 --- a/README.md +++ b/README.md @@ -286,6 +286,14 @@ export GOG_KEYRING_BACKEND=file Precedence: `GOG_KEYRING_BACKEND` env var overrides `config.json`. +Override the OS keyring service/collection/wallet name via env: + +```bash +export GOG_KEYRING_SERVICE_NAME=kdewallet +``` + +This changes the keyring service name passed to the OS backend. On KDE/KWallet this controls the wallet name used by the native KWallet backend. + ## Configuration ### Account Selection diff --git a/docs/spec.md b/docs/spec.md index a9765e50..dd354d10 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -142,6 +142,7 @@ Environment: - `GOG_CLIENT=work` (select OAuth client bucket; see `--client`) - `GOG_KEYRING_PASSWORD=...` (used when keyring falls back to encrypted file backend in non-interactive environments) - `GOG_KEYRING_BACKEND={auto|keychain|file}` (force backend; use `file` to avoid Keychain prompts and pair with `GOG_KEYRING_PASSWORD` for non-interactive) +- `GOG_KEYRING_SERVICE_NAME=...` (override keyring `ServiceName`; on native KWallet this is the wallet name) - `GOG_TIMEZONE=America/New_York` (default output timezone; IANA name or `UTC`; `local` forces local timezone) - `GOG_ENABLE_COMMANDS=calendar,tasks` (optional allowlist of top-level commands) - `config.json` can also set `keyring_backend` (JSON5; env vars take precedence) diff --git a/internal/secrets/store.go b/internal/secrets/store.go index bd3a20a5..90657d4f 100644 --- a/internal/secrets/store.go +++ b/internal/secrets/store.go @@ -49,6 +49,7 @@ func keyringItem(key string, data []byte) keyring.Item { const ( keyringPasswordEnv = "GOG_KEYRING_PASSWORD" //nolint:gosec // env var name, not a credential keyringBackendEnv = "GOG_KEYRING_BACKEND" //nolint:gosec // env var name, not a credential + keyringServiceEnv = "GOG_KEYRING_SERVICE_NAME" //nolint:gosec // env var name, not a credential ) var ( @@ -144,6 +145,14 @@ func normalizeKeyringBackend(value string) string { return strings.ToLower(strings.TrimSpace(value)) } +func keyringServiceName() string { + if value := strings.TrimSpace(os.Getenv(keyringServiceEnv)); value != "" { + return value + } + + return config.AppName +} + // keyringOpenTimeout is the maximum time to wait for keyring.Open() to complete. // On headless Linux, D-Bus SecretService can hang indefinitely if gnome-keyring // is installed but not running. @@ -185,7 +194,7 @@ func openKeyring() (keyring.Keyring, error) { } cfg := keyring.Config{ - ServiceName: config.AppName, + ServiceName: keyringServiceName(), // KeychainTrustApplication is intentionally false to support Homebrew upgrades. // When true, macOS Keychain ties access control to the specific binary hash. // Homebrew upgrades install a new binary with a different hash, causing the diff --git a/internal/secrets/store_test.go b/internal/secrets/store_test.go index 238e0537..7adaa3ab 100644 --- a/internal/secrets/store_test.go +++ b/internal/secrets/store_test.go @@ -20,7 +20,7 @@ var errKeyringOpenBlocked = errors.New("keyring open blocked") // KeychainTrustApplication is false to match production config (see store.go). func keyringConfig(keyringDir string) keyring.Config { return keyring.Config{ - ServiceName: config.AppName, + ServiceName: keyringServiceName(), KeychainTrustApplication: false, AllowedBackends: []keyring.BackendType{keyring.FileBackend}, FileDir: keyringDir, @@ -28,6 +28,22 @@ func keyringConfig(keyringDir string) keyring.Config { } } +func TestKeyringServiceName_Default(t *testing.T) { + t.Setenv(keyringServiceEnv, "") + + if got := keyringServiceName(); got != config.AppName { + t.Fatalf("expected default service name %q, got %q", config.AppName, got) + } +} + +func TestKeyringServiceName_Env(t *testing.T) { + t.Setenv(keyringServiceEnv, "kdewallet") + + if got := keyringServiceName(); got != "kdewallet" { + t.Fatalf("expected env service name %q, got %q", "kdewallet", got) + } +} + func TestResolveKeyringBackendInfo_Default(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) @@ -288,3 +304,34 @@ func TestOpenKeyring_ExplicitBackend_IgnoresDBusDetection(t *testing.T) { t.Fatal("expected non-nil store") } } + +func TestOpenKeyring_UsesEnvServiceName(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, "xdg-config")) + t.Setenv("GOG_KEYRING_BACKEND", "file") + t.Setenv("GOG_KEYRING_PASSWORD", "testpw") + t.Setenv(keyringServiceEnv, "kdewallet") + + originalOpen := keyringOpenFunc + t.Cleanup(func() { keyringOpenFunc = originalOpen }) + + var got keyring.Config + keyringOpenFunc = func(cfg keyring.Config) (keyring.Keyring, error) { + got = cfg + return keyring.NewArrayKeyring(nil), nil + } + + ring, err := openKeyring() + if err != nil { + t.Fatalf("openKeyring: %v", err) + } + + if ring == nil { + t.Fatal("expected non-nil keyring") + } + + if got.ServiceName != "kdewallet" { + t.Fatalf("expected service name %q, got %q", "kdewallet", got.ServiceName) + } +}