1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5// Package client provides a configured Lunatask API client.
6package client
7
8import (
9 "errors"
10 "fmt"
11 "os"
12 "runtime/debug"
13
14 "git.secluded.site/go-lunatask"
15 "github.com/zalando/go-keyring"
16)
17
18// EnvAPIKey is the environment variable name for the Lunatask API key.
19const EnvAPIKey = "LUNATASK_API_KEY" //nolint:gosec // not a credential, just env var name
20
21const (
22 keyringService = "lune"
23 keyringUser = "api-key"
24)
25
26// ErrNoAPIKey indicates the API key is not available from env or keyring.
27var ErrNoAPIKey = errors.New("LUNATASK_API_KEY not set and no key found in system keyring")
28
29// New creates a Lunatask client, checking env var first, then system keyring.
30func New() (*lunatask.Client, error) {
31 if token := os.Getenv(EnvAPIKey); token != "" {
32 return lunatask.NewClient(token, lunatask.UserAgent("lune/"+version())), nil
33 }
34
35 token, err := keyring.Get(keyringService, keyringUser)
36 if err != nil {
37 if errors.Is(err, keyring.ErrNotFound) {
38 return nil, ErrNoAPIKey
39 }
40
41 return nil, fmt.Errorf("accessing system keyring: %w", err)
42 }
43
44 return lunatask.NewClient(token, lunatask.UserAgent("lune/"+version())), nil
45}
46
47// GetAPIKey returns the API key from keyring. Returns empty string and nil error
48// if not found; returns error for keyring access problems.
49func GetAPIKey() (string, error) {
50 token, err := keyring.Get(keyringService, keyringUser)
51 if err != nil {
52 if errors.Is(err, keyring.ErrNotFound) {
53 return "", nil
54 }
55
56 return "", fmt.Errorf("accessing system keyring: %w", err)
57 }
58
59 return token, nil
60}
61
62// SetAPIKey stores the API key in the system keyring.
63func SetAPIKey(token string) error {
64 if err := keyring.Set(keyringService, keyringUser, token); err != nil {
65 return fmt.Errorf("keyring set: %w", err)
66 }
67
68 return nil
69}
70
71// DeleteAPIKey removes the API key from the system keyring.
72func DeleteAPIKey() error {
73 if err := keyring.Delete(keyringService, keyringUser); err != nil {
74 return fmt.Errorf("keyring delete: %w", err)
75 }
76
77 return nil
78}
79
80// HasKeyringKey checks if an API key is stored in the keyring.
81// Returns (true, nil) if found, (false, nil) if not found,
82// or (false, error) if there was a keyring access problem.
83func HasKeyringKey() (bool, error) {
84 _, err := keyring.Get(keyringService, keyringUser)
85 if err != nil {
86 if errors.Is(err, keyring.ErrNotFound) {
87 return false, nil
88 }
89
90 return false, fmt.Errorf("accessing system keyring: %w", err)
91 }
92
93 return true, nil
94}
95
96// version returns the module version from build info, or "dev" if unavailable.
97func version() string {
98 info, ok := debug.ReadBuildInfo()
99 if !ok || info.Main.Version == "" || info.Main.Version == "(devel)" {
100 return "dev"
101 }
102
103 return info.Main.Version
104}