diff --git a/README.md b/README.md index 54ff908..4573327 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Options: --port=2489 TCP port number --line-ending Convert Line Ending(CR/CRLF) --allow="0.0.0.0/0,::/0" Allow IP Range [Server only] + --socket Socket activation [Server only] --host="localhost" Destination hostname [Client only] --no-fallback-messages Do not show fallback messages [Client only] --trans-loopback=true Translate loopback address [open subcommand only] @@ -203,6 +204,35 @@ See: - [WOW! and security? · Issue #14 · lemonade-command/lemonade](https://github.com/lemonade-command/lemonade/issues/14#) +### Socket activation + +Lemonade can be activated with systemd by socket. + +Create the following files in `~/.config/systemd/user`. + +```sh +$ cat lemonade.socket +[Socket] +ListenStream=127.0.0.1:2489 +Accept=no + +[Install] +WantedBy=sockets.target +``` + +```sh +$ cat lemonade.service +[Unit] +Description=remote utility tool. +After=graphical-session.target + +[Service] +ExecStart=/usr/bin/lemonade server -socket -allow 127.0.0.1 +``` + +Enable the socket with `systemctl --user daemon-reload && systemctl --user enable --now lemonade.socket`. + +Now whenever a request is made on `localhost:2489` the lemonade server will start automatically. Links ------- diff --git a/lemon/cli.go b/lemon/cli.go index 82e8c9e..f5999a0 100644 --- a/lemon/cli.go +++ b/lemon/cli.go @@ -39,6 +39,7 @@ type CLI struct { // options Port int Allow string + Socket bool Host string TransLoopback bool TransLocalfile bool diff --git a/lemon/flag.go b/lemon/flag.go index 8b05207..eb6fb93 100644 --- a/lemon/flag.go +++ b/lemon/flag.go @@ -71,6 +71,7 @@ func (c *CLI) flags() *flag.FlagSet { flags := flag.NewFlagSet("lemonade", flag.ContinueOnError) flags.IntVar(&c.Port, "port", 2489, "TCP port number") flags.StringVar(&c.Allow, "allow", "0.0.0.0/0,::/0", "Allow IP range") + flags.BoolVar(&c.Socket, "socket", false, "Use socket activation") flags.StringVar(&c.Host, "host", "localhost", "Destination host name.") flags.BoolVar(&c.Help, "help", false, "Show this message") flags.BoolVar(&c.TransLoopback, "trans-loopback", true, "Translate loopback address") diff --git a/lemon/flag_test.go b/lemon/flag_test.go index d2aa27b..f1bddaf 100644 --- a/lemon/flag_test.go +++ b/lemon/flag_test.go @@ -4,6 +4,7 @@ import ( "os" "reflect" "testing" + "time" ) func TestCLIParse(t *testing.T) { @@ -21,16 +22,19 @@ func TestCLIParse(t *testing.T) { defaultHost := "localhost" defaultAllow := "0.0.0.0/0,::/0" defaultLogLevel := 1 + defaultTimeout := time.Duration(1e8) assert([]string{"xdg-open", "http://example.com"}, CLI{ Type: OPEN, Host: defaultHost, Port: defaultPort, Allow: defaultAllow, + Socket: false, DataSource: "http://example.com", TransLoopback: true, TransLocalfile: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) assert([]string{"/usr/bin/xdg-open", "http://example.com"}, CLI{ @@ -38,10 +42,12 @@ func TestCLIParse(t *testing.T) { Host: defaultHost, Port: defaultPort, Allow: defaultAllow, + Socket: false, DataSource: "http://example.com", TransLoopback: true, TransLocalfile: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) assert([]string{"xdg-open"}, CLI{ @@ -49,9 +55,11 @@ func TestCLIParse(t *testing.T) { Host: defaultHost, Port: defaultPort, Allow: defaultAllow, + Socket: false, TransLoopback: true, TransLocalfile: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) assert([]string{"pbpaste", "--port", "1124"}, CLI{ @@ -59,9 +67,11 @@ func TestCLIParse(t *testing.T) { Host: defaultHost, Port: 1124, Allow: defaultAllow, + Socket: false, TransLoopback: true, TransLocalfile: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) assert([]string{"/usr/bin/pbpaste", "--port", "1124"}, CLI{ @@ -69,9 +79,11 @@ func TestCLIParse(t *testing.T) { Host: defaultHost, Port: 1124, Allow: defaultAllow, + Socket: false, TransLoopback: true, TransLocalfile: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) assert([]string{"pbcopy", "hogefuga"}, CLI{ @@ -79,10 +91,12 @@ func TestCLIParse(t *testing.T) { Host: defaultHost, Port: defaultPort, Allow: defaultAllow, + Socket: false, DataSource: "hogefuga", TransLoopback: true, TransLocalfile: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) assert([]string{"/usr/bin/pbcopy", "hogefuga"}, CLI{ @@ -90,10 +104,12 @@ func TestCLIParse(t *testing.T) { Host: defaultHost, Port: defaultPort, Allow: defaultAllow, + Socket: false, DataSource: "hogefuga", TransLoopback: true, TransLocalfile: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) assert([]string{"lemonade", "--host", "192.168.0.1", "--port", "1124", "open", "http://example.com"}, CLI{ @@ -101,10 +117,12 @@ func TestCLIParse(t *testing.T) { Host: "192.168.0.1", Port: 1124, Allow: defaultAllow, + Socket: false, DataSource: "http://example.com", TransLoopback: true, TransLocalfile: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) assert([]string{"lemonade", "copy", "hogefuga"}, CLI{ @@ -112,10 +130,12 @@ func TestCLIParse(t *testing.T) { Host: defaultHost, Port: defaultPort, Allow: defaultAllow, + Socket: false, DataSource: "hogefuga", TransLoopback: true, TransLocalfile: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) assert([]string{"lemonade", "paste"}, CLI{ @@ -123,19 +143,23 @@ func TestCLIParse(t *testing.T) { Host: defaultHost, Port: defaultPort, Allow: defaultAllow, + Socket: false, TransLoopback: true, TransLocalfile: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) - assert([]string{"lemonade", "--allow", "192.168.0.0/24", "server", "--port", "1124"}, CLI{ + assert([]string{"lemonade", "--allow", "192.168.0.0/24", "server", "--port", "1124", "--socket"}, CLI{ Type: SERVER, Host: defaultHost, Port: 1124, Allow: "192.168.0.0/24", + Socket: true, TransLoopback: true, TransLocalfile: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) assert([]string{"lemonade", "open", "--trans-loopback=false"}, CLI{ @@ -143,9 +167,11 @@ func TestCLIParse(t *testing.T) { Host: defaultHost, Port: defaultPort, Allow: defaultAllow, + Socket: false, TransLoopback: false, TransLocalfile: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) assert([]string{"lemonade", "open", "--trans-loopback=true"}, CLI{ @@ -153,9 +179,11 @@ func TestCLIParse(t *testing.T) { Host: defaultHost, Port: defaultPort, Allow: defaultAllow, + Socket: false, TransLoopback: true, TransLocalfile: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) assert([]string{"lemonade", "open", "--trans-localfile=false"}, CLI{ @@ -163,9 +191,11 @@ func TestCLIParse(t *testing.T) { Host: defaultHost, Port: defaultPort, Allow: defaultAllow, + Socket: false, TransLoopback: true, TransLocalfile: false, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) assert([]string{"lemonade", "open", "--trans-localfile=true"}, CLI{ @@ -173,9 +203,11 @@ func TestCLIParse(t *testing.T) { Host: defaultHost, Port: defaultPort, Allow: defaultAllow, + Socket: false, TransLoopback: true, TransLocalfile: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) assert([]string{"lemonade", "copy", "--no-fallback-messages", "hogefuga"}, CLI{ @@ -183,11 +215,13 @@ func TestCLIParse(t *testing.T) { Host: defaultHost, Port: defaultPort, Allow: defaultAllow, + Socket: false, DataSource: "hogefuga", TransLoopback: true, TransLocalfile: true, NoFallbackMessages: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) assert([]string{"lemonade", "paste", "--no-fallback-messages"}, CLI{ @@ -195,9 +229,11 @@ func TestCLIParse(t *testing.T) { Host: defaultHost, Port: defaultPort, Allow: defaultAllow, + Socket: false, TransLoopback: true, TransLocalfile: true, NoFallbackMessages: true, LogLevel: defaultLogLevel, + Timeout: defaultTimeout, }) } diff --git a/lemon/main.go b/lemon/main.go index 62e871c..73d9a4a 100644 --- a/lemon/main.go +++ b/lemon/main.go @@ -18,6 +18,7 @@ Options: --port=2489 TCP port number --line-ending Convert Line Ending (CR/CRLF) --allow="0.0.0.0/0,::/0" Allow IP Range [Server only] + --socket Socket activation [Server only] --host="localhost" Destination hostname [Client only] --no-fallback-messages Do not show fallback messages [Client only] --trans-loopback=true Translate loopback address [open subcommand only] diff --git a/server/server.go b/server/server.go index a85275e..1f74488 100644 --- a/server/server.go +++ b/server/server.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "net/rpc" + "os" log "github.com/inconshreveable/log15" @@ -24,13 +25,23 @@ func Serve(c *lemon.CLI, logger log.Logger) error { return err } - addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", port)) - if err != nil { - return err - } - l, err := net.ListenTCP("tcp", addr) - if err != nil { - return err + var l net.Listener + + if c.Socket { + listenerFile := os.NewFile(3, "listener") + l, err = net.FileListener(listenerFile) + if err != nil { + return err + } + } else { + addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + return err + } + l, err = net.ListenTCP("tcp", addr) + if err != nil { + return err + } } for {