178 lines
5.0 KiB
Go
178 lines
5.0 KiB
Go
package daemon
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"git.jrop.me/jonathan/envvault/internal"
|
|
)
|
|
|
|
// Client provides methods to interact with the password daemon
|
|
type Client struct {
|
|
socketPath string
|
|
}
|
|
|
|
// NewClient creates a new client for the password daemon
|
|
func NewClient(socketPath string) *Client {
|
|
return &Client{
|
|
socketPath: socketPath,
|
|
}
|
|
}
|
|
|
|
// IsRunning checks if the daemon is running
|
|
func (c *Client) IsRunning() bool {
|
|
internal.DaemonLog.Printf("Checking if daemon is running at: %s", c.socketPath)
|
|
conn, err := net.Dial("unix", c.socketPath)
|
|
if err != nil {
|
|
internal.DaemonLog.Printf("Daemon is not running: %v", err)
|
|
return false
|
|
}
|
|
conn.Close()
|
|
internal.DaemonLog.Printf("Daemon is running")
|
|
return true
|
|
}
|
|
|
|
func (c *Client) EnsureRunning() error {
|
|
if c.IsRunning() {
|
|
internal.DaemonLog.Printf("Daemon is already running")
|
|
return nil
|
|
}
|
|
|
|
internal.Log.Println("Daemon not running, attempting to start it")
|
|
// Spawn daemon in background
|
|
cmd := exec.Command(os.Args[0], "daemon")
|
|
cmd.Stdout = nil
|
|
cmd.Stderr = nil
|
|
if err := cmd.Start(); err != nil {
|
|
internal.Log.Printf("Failed to start daemon: %v", err)
|
|
internal.DaemonLog.Printf("Failed to start daemon: %v", err)
|
|
} else {
|
|
internal.Log.Printf("Started daemon process with PID: %d", cmd.Process.Pid)
|
|
// Detach the process
|
|
cmd.Process.Release()
|
|
|
|
// Give daemon time to start
|
|
internal.Log.Println("Waiting for daemon to initialize")
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// StorePassword sends the password to the daemon for caching
|
|
func (c *Client) StorePassword(password []byte) error {
|
|
internal.DaemonLog.Printf("Storing password in daemon")
|
|
msg := Message{
|
|
Command: "store",
|
|
Password: string(password),
|
|
}
|
|
|
|
internal.DaemonLog.Printf("Sending store message to daemon")
|
|
resp, err := c.sendMessage(msg)
|
|
if err != nil {
|
|
internal.DaemonLog.Printf("Failed to send store message: %v", err)
|
|
return err
|
|
}
|
|
|
|
if !resp.Success {
|
|
internal.DaemonLog.Printf("Daemon reported error: %s", resp.Error)
|
|
return fmt.Errorf("failed to store password: %s", resp.Error)
|
|
}
|
|
|
|
internal.DaemonLog.Printf("Password stored successfully")
|
|
return nil
|
|
}
|
|
|
|
// RetrievePassword gets the cached password from the daemon
|
|
func (c *Client) RetrievePassword() ([]byte, error) {
|
|
internal.DaemonLog.Printf("Retrieving password from daemon")
|
|
msg := Message{
|
|
Command: "retrieve",
|
|
}
|
|
|
|
internal.DaemonLog.Printf("Sending retrieve message to daemon")
|
|
resp, err := c.sendMessage(msg)
|
|
if err != nil {
|
|
internal.DaemonLog.Printf("Failed to send retrieve message: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
if !resp.Success {
|
|
internal.DaemonLog.Printf("Daemon reported error: %s", resp.Error)
|
|
return nil, fmt.Errorf("failed to retrieve password: %s", resp.Error)
|
|
}
|
|
|
|
internal.DaemonLog.Printf("Password retrieved successfully")
|
|
return []byte(resp.Data), nil
|
|
}
|
|
|
|
// KillDaemon sends a kill command to the daemon to stop it
|
|
func (c *Client) KillDaemon() error {
|
|
internal.DaemonLog.Printf("Sending kill command to daemon")
|
|
msg := Message{
|
|
Command: "kill",
|
|
}
|
|
|
|
internal.DaemonLog.Printf("Sending kill message to daemon")
|
|
resp, err := c.sendMessage(msg)
|
|
if err != nil {
|
|
internal.DaemonLog.Printf("Failed to send kill message: %v", err)
|
|
return err
|
|
}
|
|
|
|
if !resp.Success {
|
|
internal.DaemonLog.Printf("Daemon reported error: %s", resp.Error)
|
|
return fmt.Errorf("failed to kill daemon: %s", resp.Error)
|
|
}
|
|
|
|
internal.DaemonLog.Printf("Kill command sent successfully")
|
|
return nil
|
|
}
|
|
|
|
// sendMessage sends a message to the daemon and returns the response
|
|
func (c *Client) sendMessage(msg Message) (*Response, error) {
|
|
internal.DaemonLog.Printf("Connecting to daemon socket: %s", c.socketPath)
|
|
conn, err := net.Dial("unix", c.socketPath)
|
|
if err != nil {
|
|
internal.DaemonLog.Printf("Failed to connect to daemon: %v", err)
|
|
return nil, fmt.Errorf("failed to connect to daemon: %w", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
internal.DaemonLog.Printf("Connected to daemon, encoding message")
|
|
encoder := json.NewEncoder(conn)
|
|
decoder := json.NewDecoder(conn)
|
|
|
|
if err := encoder.Encode(msg); err != nil {
|
|
internal.DaemonLog.Printf("Failed to encode message: %v", err)
|
|
return nil, fmt.Errorf("failed to encode message: %w", err)
|
|
}
|
|
|
|
internal.DaemonLog.Printf("Message sent, waiting for response")
|
|
var resp Response
|
|
if err := decoder.Decode(&resp); err != nil {
|
|
internal.DaemonLog.Printf("Failed to decode response: %v", err)
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
internal.DaemonLog.Printf("Response received: success=%v", resp.Success)
|
|
return &resp, nil
|
|
}
|
|
|
|
// GetSocketPath returns the default socket path for the current user
|
|
func GetSocketPath() string {
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
// Fall back to current directory if home dir can't be determined
|
|
internal.DaemonLog.Printf("Failed to get user home directory: %v", err)
|
|
return filepath.Join(".local/cache/envvault", "daemon.sock")
|
|
}
|
|
runtimeDir := filepath.Join(homeDir, ".local/cache/envvault")
|
|
return filepath.Join(runtimeDir, "daemon.sock")
|
|
}
|