mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
2840fdcb54
relates to: https://github.com/coder/internal/issues/1094 This is number 2 of 5 pull requests in an effort to add agent script ordering. It adds a drpc API that is exposed via a local socket. This API serves access to a lightweight DAG based dependency manager that was inspired by systemd. In follow-up PRs: * This unit manager will be plumbed into the workspace agent struct. * CLI commands will use this agentsocket api to express dependencies between coder scripts I used an LLM to produce some of these changes, but I have conducted thorough self review and consider this contribution to be ready for an external reviewer.
84 lines
2.1 KiB
Go
84 lines
2.1 KiB
Go
//go:build !windows
|
|
|
|
package agentsocket
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"golang.org/x/xerrors"
|
|
)
|
|
|
|
// createSocket creates a Unix domain socket listener
|
|
func createSocket(path string) (net.Listener, error) {
|
|
if !isSocketAvailable(path) {
|
|
return nil, xerrors.Errorf("socket path %s is not available", path)
|
|
}
|
|
|
|
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
|
return nil, xerrors.Errorf("remove existing socket: %w", err)
|
|
}
|
|
|
|
// Create parent directory if it doesn't exist
|
|
parentDir := filepath.Dir(path)
|
|
if err := os.MkdirAll(parentDir, 0o700); err != nil {
|
|
return nil, xerrors.Errorf("create socket directory: %w", err)
|
|
}
|
|
|
|
listener, err := net.Listen("unix", path)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("listen on unix socket: %w", err)
|
|
}
|
|
|
|
if err := os.Chmod(path, 0o600); err != nil {
|
|
_ = listener.Close()
|
|
return nil, xerrors.Errorf("set socket permissions: %w", err)
|
|
}
|
|
return listener, nil
|
|
}
|
|
|
|
// getDefaultSocketPath returns the default socket path for Unix-like systems
|
|
func getDefaultSocketPath() (string, error) {
|
|
randomBytes := make([]byte, 4)
|
|
if _, err := rand.Read(randomBytes); err != nil {
|
|
return "", xerrors.Errorf("generate random socket name: %w", err)
|
|
}
|
|
randomSuffix := hex.EncodeToString(randomBytes)
|
|
|
|
// Try XDG_RUNTIME_DIR first
|
|
if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
|
|
return filepath.Join(runtimeDir, "coder-agent-"+randomSuffix+".sock"), nil
|
|
}
|
|
|
|
return filepath.Join("/tmp", "coder-agent-"+randomSuffix+".sock"), nil
|
|
}
|
|
|
|
// CleanupSocket removes the socket file
|
|
func cleanupSocket(path string) error {
|
|
return os.Remove(path)
|
|
}
|
|
|
|
// isSocketAvailable checks if a socket path is available for use
|
|
func isSocketAvailable(path string) bool {
|
|
// Check if file exists
|
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
return true
|
|
}
|
|
|
|
// Try to connect to see if it's actually listening
|
|
dialer := net.Dialer{Timeout: 10 * time.Second}
|
|
conn, err := dialer.Dial("unix", path)
|
|
if err != nil {
|
|
// If we can't connect, the socket is not in use
|
|
// Socket is available for use
|
|
return true
|
|
}
|
|
_ = conn.Close()
|
|
// Socket is in use
|
|
return false
|
|
}
|