systemd – Connect to stdin/stdout After Service Has Started

iosystemd

I have a systemd service that is a console application, meaning that it is controlled by sending commands to its stdin and it outputs information to sdout. How can I set up the systemd service so that I can connect to its stdin and give it commands at any point, then detach from this, and repeat when necessary?

Best Answer

I can think of multiple ways to do this. Of course, each has its own caveats.

  1. Probably the most straightforward approach would be to create a simple service with a dedicated tty, similar to this:

    # /etc/systemd/system/systemd-interactive-simple-tty.service
    
    [Unit]
    Description=Example systemd interactive simple tty service
    After=getty.service
    
    [Service]
    # https://www.freedesktop.org/software/systemd/man/systemd.exec.html
    ExecStart=/usr/local/sbin/systemd-interactive.bash
    StandardInput=tty-force
    TTYVHangup=yes
    TTYPath=/dev/tty20
    TTYReset=yes
    # https://www.freedesktop.org/software/systemd/man/systemd.service.html
    Type=simple
    RemainAfterExit=false
    Restart=always
    RestartSec=5s
    
    [Install]
    WantedBy=default.target
    

    The following options will work with the above simple service:

    1. conspy takes (remote) control of a text mode virtual console. This is probably your best bet (with the above tty service). It's available via most extended package repositories and is simple to use, like this:

       conspy 20 # hit ESC+ESC+ESC (3 times quickly, to exit)
      
    2. chvt works similarly to conspy but makes /dev/ttyN the foreground (local) terminal. It's part of the kbd collection and is installed by default on virtually every modern Linux distribution. That's why I thought it was worth a mention. The major caveat with chvt is that it requires you to use the attached keyboard, which is probably not what you want. For the above service example, chvt could be used like this:

       chvt 20 # ALT+F1 to return to /dev/tty1
      
    3. reptyr uses the ptrace(2) system call to attach to a remote program (via it's PID). This is a completely different approach than conspy & chvt, but would work with the above service definition too.

      Just keep in mind that reptyr, by itself, doesn't really support 'detaching'. Its termcap support isn't very robust, either. Typically, reptyr is used in conjunction with screen and/or tmux because they provide a more seamless way to 'detach'; I find reptyr is a great, niche tool to move existing PIDs into a screen session or tmux window or pane.

      That said; I put this option here, albeit last, because it's still possible to use reptyr without screen or tmux. The major caveat is if you break out of the process (e.g ^C), rather than reptyr'ing it (again) to another tty/pty (via another shell). Sending a break to the process may cause it to abort and I'm sure you know the rest.

      Maybe that's OK, especially if the process isn't critical and the systemd service is configured to Restart=always as I've shown above. If the process 'breaks' then systemd will automatically restart it (another cool feature of systemd!). There are different values for Restart, too. YMMV.

      reptyr is available via most extended package repositories and can be used, like this:

       reptyr $(systemctl status systemd-interactive-simple-tty.service | grep Main\ PID | awk '{print $3}') # or just reptyr <pid>
      
  2. Another (more complex [meaning there's more that could fail]) approach would be to create a forking service using screen, similar to this:

    # /etc/systemd/system/systemd-interactive-forking-screen.service
    
    [Unit]
    Description=Example systemd interactive forking screen service
    
    [Service]
    # https://www.freedesktop.org/software/systemd/man/systemd.exec.html
    ExecStartPre=-/usr/bin/screen -X -S ${SCREEN_TITLE} kill # [optional] prevent multiple screens with the same name
    ExecStart=/usr/bin/screen -dmS ${SCREEN_TITLE} -O -l /usr/bin/bash -c /usr/local/sbin/systemd-interactive.bash
    # https://www.freedesktop.org/software/systemd/man/systemd.service.html
    Type=forking
    Environment=SCREEN_TITLE=systemd-interactive
    RemainAfterExit=false
    Restart=always
    RestartSec=5s
    SuccessExitStatus=1
    
    [Install]
    WantedBy=default.target
    

    screen is a full-screen window manager that multiplexes a physical terminal between several processes. It's quite a bit more complex than anything listed in the first, simple option. Personally, I've been using screen for a long, long time and feel comfortable enough to trust it with most things. It's an invaluable tool.

    The primary advantage over the above is decent termcap support (though not as good as tmux's). That just means that your backspace key, arrows, etc. will work better than with conspy or reptyr. screen is available via most base package repositories, and can be used like this:

    screen -r systemd-interactive # CTRL-A+D to detach
    
  3. A similar approach to forking screen would be to fork tmux. The systemd service for tmux is almost the same as it is for screen. But, I'm not going to detail this because, well, it's late & I'm tired. Yes, I use tmux a lot more than screen (these days).

    In fact, I'm writing this in neovim pane in tmux right now. But, I've still used screen for a lot longer. In my experience and opinion, tmux is overkill for something like this. Sure tmux is newer, has more features, and is a MUCH better shell multiplexer than screen but ... it's even more complex. Along with that extra complexity comes some additional instability.

    More important, to me at least, is that tmux crashes more often than screen. I listed screen as #2 because, if it were me, for something like that I'd probably just use #1 with conspy.

  4. Depending on your program; named pipes ... systemd services support them, too! i.e.

    StandardInput=/path/to/named/pipe|
    

... and more.

Related Question