3

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? Sep 30, 2020 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, 2020 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, 2020 at 9:19

1 Answer 1

7

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, 2020 at 10:33

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .