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 } // 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") }