summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-17 08:45:00 +0300
committerPaul Buetow <paul@buetow.org>2026-04-17 08:45:00 +0300
commite4671dc15d224d944fe5c3715c95793c21167443 (patch)
treee63b9a11d24839b2780ce1893bcd7e75f5f0cd9f
parent426b9531c32d1afb154feea6e9818d59ff73d696 (diff)
feat: add backend config fields for KeePass support (task h4)
Add Backend (default 'geheim'), KDBXPath, KDBXKeyFile, KDBXPassFile fields to Config. Defaults mirror migrate_kdbx.go hardcoded values. Extend expandPathFieldsWithHome for the three path fields. Table-driven tests cover defaults, JSON override, and tilde expansion. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--internal/config/config.go29
-rw-r--r--internal/config/config_test.go70
2 files changed, 99 insertions, 0 deletions
diff --git a/internal/config/config.go b/internal/config/config.go
index b8d0532..3b4bed4 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -22,6 +22,8 @@ const (
// Config holds all application-wide configuration values.
// JSON field names use snake_case to match the original geheim.rb Config::DEFAULTS keys.
+// The Backend field selects the storage backend: "geheim" (default, encrypted
+// .index/.data files in a git repo) or "keepass" (a .kdbx database file).
type Config struct {
DataDir string `json:"data_dir"`
ExportDir string `json:"export_dir"`
@@ -33,6 +35,19 @@ type Config struct {
GnomeClipboardCmd string `json:"gnome_clipboard_cmd"`
MacOSClipboardCmd string `json:"macos_clipboard_cmd"`
SyncRepos []string `json:"sync_repos"`
+
+ // Backend selects the storage backend: "geheim" (default) or "keepass".
+ Backend string `json:"backend"`
+ // KDBXPath is the path to the KeePass .kdbx database file.
+ // Defaults to ~/Documents/Keepass/master to match migrate-kdbx defaults.
+ KDBXPath string `json:"kdbx_path"`
+ // KDBXKeyFile is the optional path to a KeePass key file.
+ // An empty value disables key file authentication.
+ KDBXKeyFile string `json:"kdbx_key_file"`
+ // KDBXPassFile is the optional path to a file containing the KeePass password.
+ // Defaults to ~/.master.pass to match migrate-kdbx defaults.
+ // When empty, the password is read interactively at startup.
+ KDBXPassFile string `json:"kdbx_pass_file"`
}
// resolveHomeDir resolves the current user's home directory from OS state.
@@ -99,6 +114,15 @@ func defaultConfigWithHome(home string) Config {
GnomeClipboardCmd: "gpaste-client",
MacOSClipboardCmd: "pbcopy",
SyncRepos: []string{"git1", "git2"},
+
+ // Backend defaults match the geheim (original) storage backend.
+ // KDBXPath and KDBXPassFile defaults mirror the migrate-kdbx command
+ // at internal/cli/migrate_kdbx.go so users who already use that command
+ // have zero additional configuration to provide.
+ Backend: "geheim",
+ KDBXPath: filepath.Join(home, "Documents", "Keepass", "master"),
+ KDBXKeyFile: "",
+ KDBXPassFile: filepath.Join(home, ".master.pass"),
}
}
@@ -126,10 +150,15 @@ func expandTilde(path string) string {
}
// expandPathFieldsWithHome tilde-expands every path-typed field in cfg in place.
+// This covers both the geheim fields (DataDir, ExportDir, KeyFile) and the
+// KeePass fields (KDBXPath, KDBXKeyFile, KDBXPassFile).
func expandPathFieldsWithHome(cfg *Config, home string) {
cfg.DataDir = expandTildeWithHome(cfg.DataDir, home)
cfg.ExportDir = expandTildeWithHome(cfg.ExportDir, home)
cfg.KeyFile = expandTildeWithHome(cfg.KeyFile, home)
+ cfg.KDBXPath = expandTildeWithHome(cfg.KDBXPath, home)
+ cfg.KDBXKeyFile = expandTildeWithHome(cfg.KDBXKeyFile, home)
+ cfg.KDBXPassFile = expandTildeWithHome(cfg.KDBXPassFile, home)
}
// expandPathFields tilde-expands every path-typed field in cfg in place.
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 4b2e7d9..7ffcc68 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -286,6 +286,76 @@ func TestLoad_missing_file_no_warning(t *testing.T) {
}
}
+// TestLoad_backendDefaults verifies that the backend-related fields have the
+// expected default values (matching migrate-kdbx defaults) when no config file
+// exists.
+func TestLoad_backendDefaults(t *testing.T) {
+ dir := t.TempDir()
+ t.Setenv("HOME", dir)
+
+ cfg := Load()
+
+ cases := []struct{ name, got, want string }{
+ {"Backend", cfg.Backend, "geheim"},
+ {"KDBXPath", cfg.KDBXPath, filepath.Join(dir, "Documents", "Keepass", "master")},
+ {"KDBXKeyFile", cfg.KDBXKeyFile, ""},
+ {"KDBXPassFile", cfg.KDBXPassFile, filepath.Join(dir, ".master.pass")},
+ }
+ for _, tc := range cases {
+ if tc.got != tc.want {
+ t.Errorf("%s = %q; want %q", tc.name, tc.got, tc.want)
+ }
+ }
+}
+
+// TestLoad_backendJSONOverride verifies that backend config fields can be
+// overridden via the JSON config file and that absent fields keep their defaults.
+func TestLoad_backendJSONOverride(t *testing.T) {
+ dir := t.TempDir()
+ t.Setenv("HOME", dir)
+ writeUserConfig(t, dir, `{"backend":"keepass","kdbx_path":"~/vaults/work.kdbx","kdbx_key_file":"~/vaults/work.key"}`)
+
+ cfg := Load()
+
+ cases := []struct{ name, got, want string }{
+ {"Backend", cfg.Backend, "keepass"},
+ {"KDBXPath", cfg.KDBXPath, filepath.Join(dir, "vaults", "work.kdbx")},
+ {"KDBXKeyFile", cfg.KDBXKeyFile, filepath.Join(dir, "vaults", "work.key")},
+ // KDBXPassFile not in JSON — must keep expanded default.
+ {"KDBXPassFile", cfg.KDBXPassFile, filepath.Join(dir, ".master.pass")},
+ }
+ for _, tc := range cases {
+ if tc.got != tc.want {
+ t.Errorf("%s = %q; want %q", tc.name, tc.got, tc.want)
+ }
+ }
+}
+
+// TestLoad_backendTildeExpansion verifies that tilde paths for all three KDBX
+// path fields are expanded to absolute paths after Load().
+func TestLoad_backendTildeExpansion(t *testing.T) {
+ dir := t.TempDir()
+ t.Setenv("HOME", dir)
+ writeUserConfig(t, dir, `{
+ "kdbx_path": "~/kp/db.kdbx",
+ "kdbx_key_file": "~/kp/db.key",
+ "kdbx_pass_file": "~/kp/db.pass"
+ }`)
+
+ cfg := Load()
+
+ cases := []struct{ name, got, want string }{
+ {"KDBXPath", cfg.KDBXPath, filepath.Join(dir, "kp", "db.kdbx")},
+ {"KDBXKeyFile", cfg.KDBXKeyFile, filepath.Join(dir, "kp", "db.key")},
+ {"KDBXPassFile", cfg.KDBXPassFile, filepath.Join(dir, "kp", "db.pass")},
+ }
+ for _, tc := range cases {
+ if tc.got != tc.want {
+ t.Errorf("%s = %q; want %q", tc.name, tc.got, tc.want)
+ }
+ }
+}
+
// TestLoad_unreadable_file verifies that a config file that exists but cannot
// be read emits a warning and returns defaults (the !os.IsNotExist branch).
// EDITOR is unset so EditCmd falls back to "vi".