1

I have a service running which has a CLI connected to stdin. When I ssh into the machine, I'd like to be able to send commands to the stdin of that service.

systemd.exec says that StandardInput=file:/path/to/file is a thing and supports FIFOs. That sounds like the simplest method.

I've tried this in my ~/.config/systemd/user/foo.service

[Service]
ExecStartPre=mkfifo %t/foo.stdin
ExecStart=cat -
StandardInput=file:%t/foo.stdin
ExecStopPost=rm -f %t/foo.stdin

In this example, I expect that when I run the following, I'll see the output echo'd in the journal.

echo "hello" > /run/user/1000/foo.stdin

I have two problems with this:

  1. foo.service: Failed to set up standard input: No such file or directory . It appears that StandardInput= must exist before ExecStartPre=. Will I need to create a permanent pipe in a static location during installation or is there a work-around? If I make the file manually, and remove ExecStartPre/ExecStopPost, then things work.
  2. The first echo "command" is processed fine, but sends an EOF and stdin closes. I wanted the stdin to remain open. The answer (exec 3> stdin, ..., exec 3>&-) seems to be to use bash FD redirection, but that isn't available in systemd.
3
  • You can just point ExecStart to a shell script that will set up the pipe and chainload the real executable. But if I may challenge the framing, why not use ncat/netcat and Unix sockets instead of pipes? – user3840170 Sep 30 '20 at 9:04
  • I know how to use netcat to listen on a specific port, but I'd prefer the user to log-in (instead of listening to any traffic on that port port). I haven't used netcat with unix sockets. – Stewart Sep 30 '20 at 9:08
  • The other problem with using netcat, is my service doesn't call accept(). That means foo.socket needs Accept=yes. But that means the socket will spawn one instance of the service per connection, then stop the service when the connection completes. – Stewart Sep 30 '20 at 9:19
4

You can create a socket unit for your FIFO:

  • foo.socket:

    [Socket]
    ListenFIFO=%t/foo.stdin
    Service=foo.service
    
  • foo.service:

    [Service]
    ExecStart=/bin/cat -
    Sockets=foo.socket
    StandardInput=socket
    StandardOutput=journal
    StandardError=journal
    

Start it up as follows:

systemctl --user start foo.socket

The service itself will start as soon as the FIFO is written into. If you want it to start immediately, instead launch it like this:

systemctl --user start foo.service
1
  • Works like a charm. Thanks. I've never used the ListenFIFO= option before. It's way simpler than I thought. – Stewart Sep 30 '20 at 10:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.