m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-08 11:23:47 -05:00

Add Unix domain socket support for --listen

Close #4541
This commit is contained in:
Junegunn Choi
2025-10-09 01:05:26 +09:00
parent c38c6cad79
commit 01cb38a5fb
5 changed files with 87 additions and 26 deletions

View File

@@ -124,6 +124,24 @@ This version includes a few minor updates to fzf's classic visual style:
- Markers no longer use background colors.
- The `--color base16` theme (alias: `16`) has been updated for better compatibility with both dark and light themes.
### `--listen` now supports Unix domain sockets
If an argument to `--listen` ends with `.sock`, fzf will listen on a Unix
domain socket at the specified path.
```sh
fzf --listen /tmp/fzf.sock --no-tmux
# GET
curl --unix-socket /tmp/fzf.sock http
# POST
curl --unix-socket /tmp/fzf.sock http -d up
```
Note that any existing file at the given path will be removed before creating
the socket, so avoid using an important file path.
### Added options
#### `--gutter CHAR`

View File

@@ -1133,19 +1133,25 @@ On Windows, the default value is \fBcmd /s/c\fR when \fB$SHELL\fR is not
set.
.TP
.B "\-\-listen[=[ADDR:]PORT]" "\-\-listen\-unsafe[=[ADDR:]PORT]"
Start HTTP server and listen on the given address. It allows external processes
to send actions to perform via POST method.
.B "\-\-listen[=SOCKET_PATH|[ADDR:]PORT]" "\-\-listen\-unsafe[=[ADDR:]PORT]"
Start HTTP server and listen on the given address or Unix socket. It allows
external processes to send actions to perform via POST method and query the
program state via GET method. For the argument to be recognized as a socket
path, it must have \fB.sock\fR extension.
- If the port number is omitted or given as 0, fzf will automatically choose
a port and export it as \fBFZF_PORT\fR environment variable to the child processes
a port and export it as \fBFZF_PORT\fR environment variable to the child processes.
- If a Unix socket path is given, fzf will create a Unix domain socket at the
given path. The existing file will be removed. The path to the socket file
is exported as \fBFZF_SOCK\fR environment variable.
- If \fBFZF_API_KEY\fR environment variable is set, the server would require
sending an API key with the same value in the \fBx\-api\-key\fR HTTP header
sending an API key with the same value in the \fBx\-api\-key\fR HTTP header.
- \fBFZF_API_KEY\fR is required for a non-localhost listen address
- \fBFZF_API_KEY\fR is required for a non-localhost listen address.
- To allow remote process execution, use \fB\-\-listen\-unsafe\fR
- To allow remote process execution, use \fB\-\-listen\-unsafe\fR.
e.g.
\fB# Start HTTP server on port 6266
@@ -1184,6 +1190,18 @@ e.g.
'
\fR
Here is an example script that uses a Unix socket instead of a TCP port.
\fB
fzf --listen=/tmp/fzf.sock
# GET
curl --unix-socket /tmp/fzf.sock http
# POST
curl --unix-socket /tmp/fzf.sock http -d up
\fR
.SS DIRECTORY TRAVERSAL
.TP
.B "\-\-walker=[file][,dir][,follow][,hidden]"
@@ -1373,6 +1391,8 @@ fzf exports the following environment variables to its child processes.
.br
.BR FZF_PORT " Port number when \-\-listen option is used"
.br
.BR FZF_SOCK " Unix socket path when \-\-listen option is used"
.br
.BR FZF_PREVIEW_TOP " Top position of the preview window"
.br
.BR FZF_PREVIEW_LEFT " Left position of the preview window"

View File

@@ -206,7 +206,9 @@ Usage: fzf [options]
ADVANCED
--with-shell=STR Shell command and flags to start child processes with
--listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
--listen[=SOCKET_PATH] Start HTTP server to receive actions via Unix domain socket
(Path should end with .sock)
--listen[=[ADDR:]PORT] Start HTTP server to receive actions via TCP
(To allow remote process execution, use --listen-unsafe)
DIRECTORY TRAVERSAL (Only used when $FZF_DEFAULT_COMMAND is not set)

View File

@@ -46,15 +46,20 @@ type httpServer struct {
type listenAddress struct {
host string
port int
sock string
}
func (addr listenAddress) IsLocal() bool {
return addr.host == "localhost" || addr.host == "127.0.0.1"
return addr.host == "localhost" || addr.host == "127.0.0.1" || len(addr.sock) > 0
}
var defaultListenAddr = listenAddress{"localhost", 0}
var defaultListenAddr = listenAddress{"localhost", 0, ""}
func parseListenAddress(address string) (listenAddress, error) {
if strings.HasSuffix(address, ".sock") {
return listenAddress{"", 0, address}, nil
}
parts := strings.SplitN(address, ":", 3)
if len(parts) == 1 {
parts = []string{"localhost", parts[0]}
@@ -70,7 +75,7 @@ func parseListenAddress(address string) (listenAddress, error) {
if len(parts[0]) == 0 {
parts[0] = "localhost"
}
return listenAddress{parts[0], port}, nil
return listenAddress{parts[0], port, ""}, nil
}
func startHttpServer(address listenAddress, actionChannel chan []*action, getHandler func(getParams) string) (net.Listener, int, error) {
@@ -80,8 +85,18 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, getHan
if !address.IsLocal() && len(apiKey) == 0 {
return nil, port, errors.New("FZF_API_KEY is required to allow remote access")
}
var listener net.Listener
var err error
if len(address.sock) > 0 {
os.Remove(address.sock)
listener, err = net.Listen("unix", address.sock)
if err != nil {
return nil, 0, fmt.Errorf("failed to listen on %s", address.sock)
}
} else {
addrStr := fmt.Sprintf("%s:%d", host, port)
listener, err := net.Listen("tcp", addrStr)
listener, err = net.Listen("tcp", addrStr)
if err != nil {
return nil, port, fmt.Errorf("failed to listen on %s", addrStr)
}
@@ -97,6 +112,7 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, getHan
return nil, port, err
}
}
}
server := httpServer{
apiKey: []byte(apiKey),

View File

@@ -1268,8 +1268,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
return nil, err
}
t.listener = listener
if port > 0 {
t.listenPort = &port
}
}
if t.hasStartActions {
t.eventChan <- tui.Start.AsEvent()
@@ -1292,6 +1294,9 @@ func (t *Terminal) environForPreview() []string {
func (t *Terminal) environImpl(forPreview bool) []string {
env := os.Environ()
if t.listenAddr != nil && len(t.listenAddr.sock) > 0 {
env = append(env, "FZF_SOCK="+t.listenAddr.sock)
}
if t.listenPort != nil {
env = append(env, fmt.Sprintf("FZF_PORT=%d", *t.listenPort))
}