package client import ( "encoding/json" "errors" "os" "path/filepath" ) // Config is what a client remembers between runs. The session token lives // here; the KDBX master passwords never do, because the client never opens a // database, it only moves bytes. type Config struct { ServerURL string `json:"server_url"` Token string `json:"token"` Username string `json:"username"` MirrorDir string `json:"mirror_dir"` // PollSeconds is how often the mirror is rescanned for local saves. PollSeconds int `json:"poll_seconds"` } // ErrNoConfig means the client has not been logged in yet. var ErrNoConfig = errors.New("not logged in: run `keeporg login` first") // DefaultConfigPath is the per-user configuration path on this platform. func DefaultConfigPath() string { dir, err := os.UserConfigDir() if err != nil { return filepath.Join(".", ".keeporg", "config.json") } return filepath.Join(dir, "keeporg", "config.json") } // DefaultMirrorDir is where databases are mirrored unless told otherwise. func DefaultMirrorDir() string { home, err := os.UserHomeDir() if err != nil { return "keeporg" } return filepath.Join(home, "KeepOrg") } // LoadConfig reads a configuration file. func LoadConfig(path string) (*Config, error) { b, err := os.ReadFile(path) if os.IsNotExist(err) { return nil, ErrNoConfig } if err != nil { return nil, err } var c Config if err := json.Unmarshal(b, &c); err != nil { return nil, err } if c.MirrorDir == "" { c.MirrorDir = DefaultMirrorDir() } if c.PollSeconds <= 0 { c.PollSeconds = 2 } return &c, nil } // Save writes the configuration with owner-only permissions, since it holds // a bearer token. func (c *Config) Save(path string) error { if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { return err } b, err := json.MarshalIndent(c, "", " ") if err != nil { return err } tmp := path + ".tmp" if err := os.WriteFile(tmp, append(b, '\n'), 0o600); err != nil { return err } return os.Rename(tmp, path) }