Skip to main content

Auto-starting things in GNU screen

·5 mins
Kristof Kovacs
Author
Kristof Kovacs
Software Architect & DevOps Consultant

Hello, I’m Kristof, a human being like you, and an easy to work with, friendly guy.

I've been a programmer, a consultant, CIO in startups, head of software development in government, and built two software companies.

Some days I’m coding Golang in the guts of a system and other days I'm wearing a suit to help clients with their DevOps practices.

GNU screen is one of my must-have command-line tools. It protects against disconnects, it multiplexes SSH connections, it gives us shared screens, it highlights our PROD servers, I use it for copy-paste, logging – everything.

an example of gnu screen running

1. Auto-starting things in GNU screen AT BOOT #

Sometimes you want something to survive a reboot and keep running, but you need more control over it than running from systemd – maybe because you are debugging it, or maybe you don't have access.

In that case, here's a handy one-liner to put into crontab -e:

@reboot /usr/bin/screen -dm -s /bin/bash && screen -X stuff 'cd ~/SOME-DIRECTORY-HERE; while true; do ( python3 -m http.server 80; sleep 1 ); done^M'
NOTE: I'm using python3 -m http.server 80 as the example command to run. Replace that part with your own command.

What it does:

  1. The @reboot runs this command at machine boot.
  2. It starts a background screen (screen -dm) with /bin/bash as the shell (cron often has /bin/sh as default shell, this makes it more user-friendly)
  3. The screen -X stuff "..."part uses GNU screen's stuff command to "type" our command into the previously background-started instance.
  4. The ^M part types an Enter and executes the command.

Software run like this can be restarted manually in GNU screen with ctrl-c, or with a kill, and the loop keeps it running.

Pro tip #

If your command is running on a TCP port (say, port 80), this kills/restarts it too:

fuser -sk 80/tcp

2. Starting things in GNU screen FROM SCRIPTS #

I use these often for run-dev.sh scripts, which build/start temporary development environments.

These snippets are built so they can be started either from outside or inside of GNU screen, and they will work both ways.

When running things with these, you can later go to the GNU screen tab where your program is running, ctrl-c the running program, and the tab won't exit or close. You can make modifications, and then restart your program just with Up, Enter.

NOTE: I'm using python3 -m http.server 80 as the example command to run. Replace that part with your own command.

Case "A": runs on TCP port, RESTARTS program on script re-run #

When you run something that runs on a TCP port (port 80, in this example, like a web server) and you want the program restart if the script is re-run:

fuser -sk 80/tcp || (
	screen -ls >/dev/null &&
	screen -X screen ||
	screen -dm &&
	screen -X stuff "while true; do (cd \"$(pwd)\"; python3 -m http.server 80; sleep 1; ); done^M"
)

What it does:

  1. Check if the program is running on port 80 (fuser). If yes, kill it (-k), and then the presumably already running while loop (see below) will restart it. No further action is done.
  2. If the program is NOT running (screen -ls >/dev/null && .. || ...), either start a background GNU screen (screen -dm) or open a new tab in existing one (screen -X screen) if already running.
  3. Uses screen's stuff command to type in the command that runs our program in a loop. (This part: screen -X stuff "...". The ^M part hits Enter.)
  4. Use cd \"$(pwd)\" to switch to the current dir, even if GNU screen was previously started from another dir (and so uses that dir as default for new tabs).
  5. Because of using stuff (and not passing the command as a parameter), the loop is also manually restart-able in screen with ctrl-c and Up, Enter.

Works the same when started either from inside or outside of screen. (For running from crontab, check above.)

Case "B": NOT uses TCP port, RESTARTS program on script re-run #

If the command you want to run does NOT use a TCP port, we can use a lock file instead, let's say, /tmp/runlock:

fuser -sk /tmp/runlock || (
	screen -ls >/dev/null &&
	screen -X screen ||
	screen -dm &&
	screen -X stuff "while true; do (cd \"$(pwd)\"; python3 -m http.server 80; sleep 1; ) 9>/tmp/runlock; done^M"
)

In this case, the 9>/tmp/runlock keeps the /tmp/runlock file open while our command is running, and the fuser part detects that.

Cases "C" and "D": same as "A" and "B", but NO RESTARTING on script re-run #

Basically "ensure running", but don't touch if it's already running. (Also called "idempotent", in Ansible terminology).

You can remove the k from the previous fuser to NOT kill the program if already running. Then the loop is not needed either, but you could keep it to restart the program in case it exits.

Ensure running (no restart) with TCP port 80:

fuser -s 80/tcp || (
	screen -ls >/dev/null &&
	screen -X screen ||
	screen -dm &&
	screen -X stuff "cd \"$(pwd)\"; python3 -m http.server 80;^M"
)

Ensure running (no restart) with lock file /tmp/runlock:

fuser -s /tmp/runlock || (
	screen -ls >/dev/null &&
	screen -X screen ||
	screen -dm &&
	screen -X stuff "cd \"$(pwd)\"; python3 -m http.server 80 9>/tmp/runlock;^M"
)

Pro tip #

By default the above snippets will start the program in the "background" (a new GNU screen, or a new tab in an existing GNU screen), and continue your script.

It's useful for starting multiple programs in the background (for a test, for example), but often when everything is up and running, you want to jump into the GNU screen to see what's going on.

At the end of your script, this snippet "attaches" to the GNU screen if not running in screen already, but does nothing if you're already inside the GNU screen:

[ -z "$STY" ] && screen -p 0 -xR

P.s.: .screenrc #

In case you are interested in my .screenrc for the above screenshot, it's online here.


Questions? Comments? Insults?
Feel free to drop me a line below, I love getting messages!