-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathkeyring.go
More file actions
129 lines (108 loc) · 3.38 KB
/
keyring.go
File metadata and controls
129 lines (108 loc) · 3.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package devflow
import (
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"github.com/zalando/go-keyring"
)
// Keyring service name for storing secrets
const keyringService = "devflow"
// Keyring provides secure credential storage using the system keyring
type Keyring struct {
log func(...any)
}
// NewKeyring creates a keyring handler and ensures dependencies are installed
func NewKeyring() (*Keyring, error) {
k := &Keyring{
log: func(...any) {},
}
if err := k.ensureKeyringAvailable(); err != nil {
return nil, err
}
return k, nil
}
// SetLog sets the logging function for the keyring handler
func (k *Keyring) SetLog(fn func(...any)) {
if fn != nil {
k.log = fn
}
}
// Set stores a secret in the keyring
func (k *Keyring) Set(key, value string) error {
return keyring.Set(keyringService, key, value)
}
// Get retrieves a secret from the keyring
func (k *Keyring) Get(key string) (string, error) {
return keyring.Get(keyringService, key)
}
// Delete removes a secret from the keyring
func (k *Keyring) Delete(key string) error {
return keyring.Delete(keyringService, key)
}
// ensureKeyringAvailable checks if keyring is working and installs dependencies if needed
func (k *Keyring) ensureKeyringAvailable() error {
// Test if keyring is working
testKey := "devflow_keyring_test"
err := keyring.Set(keyringService, testKey, "test")
if err == nil {
keyring.Delete(keyringService, testKey)
return nil
}
// Keyring failed - try to install on Linux only
if runtime.GOOS != "linux" {
return fmt.Errorf("keyring unavailable: %w", err)
}
k.log("⚙️ Installing keyring dependencies...")
if !k.tryInstallKeyring() {
return fmt.Errorf("could not install keyring. Install manually:\n Debian/Ubuntu: sudo apt install gnome-keyring libsecret-1-0\n Fedora: sudo dnf install gnome-keyring libsecret\n Arch: sudo pacman -S gnome-keyring libsecret")
}
k.startKeyringService()
// Test again
err = keyring.Set(keyringService, testKey, "test")
if err == nil {
keyring.Delete(keyringService, testKey)
k.log("✅ Keyring installed successfully")
return nil
}
return fmt.Errorf("keyring installation failed: %w", err)
}
// tryInstallKeyring attempts to install keyring using available package manager
func (k *Keyring) tryInstallKeyring() bool {
type pkgManager struct {
cmd string
args []string
}
managers := []pkgManager{
{"apt", []string{"sudo", "apt", "install", "-y", "gnome-keyring", "libsecret-1-0"}},
{"dnf", []string{"sudo", "dnf", "install", "-y", "gnome-keyring", "libsecret"}},
{"pacman", []string{"sudo", "pacman", "-S", "--noconfirm", "gnome-keyring", "libsecret"}},
}
for _, m := range managers {
if _, err := exec.LookPath(m.cmd); err == nil {
k.log(fmt.Sprintf(" Installing via %s...", m.cmd))
cmd := exec.Command(m.args[0], m.args[1:]...)
// We don't pipe to os.Stdout anymore to keep it quiet unless logged
if cmd.Run() == nil {
return true
}
}
}
return false
}
// startKeyringService starts gnome-keyring-daemon if not running
func (k *Keyring) startKeyringService() {
if _, err := exec.LookPath("gnome-keyring-daemon"); err != nil {
return
}
cmd := exec.Command("gnome-keyring-daemon", "--start", "--components=secrets")
output, err := cmd.Output()
if err == nil {
for _, line := range strings.Split(string(output), "\n") {
if parts := strings.SplitN(line, "=", 2); len(parts) == 2 {
os.Setenv(parts[0], parts[1])
}
}
}
}