Installing Services in One Line
Installing e.g. web services on a computer is a lot more complicated than what it should be. By pretending that everything is on a LAN, you can make things a bit simpler... but then... having something run all the time is a lot harder than what it should be. You need to... write an init script? Create a systemd unit? (... what were the potential options again?)
Even though it could be stupidly simple.
The problem
I'm a fan of the s6 init system. It's as UNIX as it gets; it's lean, it is holding onto your processes instead of tracking them in broken ways via PID files; also, it has an entire scripting language that is based on just nesting UNIX commands into each other with no actual shell running (it is pretty cool in a "pretty math" way).
The way it works is... you have a /service
directory, the subdirectories of which describe services. (... this "tree for configs" idea also feels familiar somehow.) You have /service/our_thing/run
, which is an arbitrary executable that will launch the service. (... whenever it quits, the service is dead; "detaching" and similar weird things aren't needed here.) Meanwhile, /service/our_thing/log/run
is yet another script which will get connected to the stdout of our process; what it prints goes to a log file.
Taking our Lisp service as an example, here is how a run
file looks like:
#!/usr/local/bin/execlineb -P
/usr/local/bin/s6-setuidgid simon # run as me
/usr/local/bin/fdmove -c 2 1 # redirect stderr to stdout
/usr/bin/sbcl --load /home/simon/local/slime/start-swank-unix.lisp
It is... nice, but... to set up all these things... and then realize that you got it wrong, edit again etc... it's... really not fun.
It's really not much prettier with systemd either:
[Unit]
Description=My Service Running SBCL with Slime
[Service]
Type=simple
User=simon
Group=simon
ExecStart=/usr/bin/sbcl --load /home/simon/local/slime/start-swank-unix.lisp
StandardOutput=inherit
StandardError=inherit
[Install]
WantedBy=multi-user.target
It... might be subtly wrong (... as everything that GPT 4 produces in about 10 seconds); the point is that it might be subtly wrong... also, remembering all these is... nontrivial, unless your main job involves setting up services regularly.
(There aren't a lot of people whose main job involves setting up services regularly.)
A solution
You should be able to say something like:
/usr/bin/sbcl --load /home/simon/local/slime/start-swank-unix.lisp
... and then watch the thing run. Or crash, depending. (It's way easier to fix crashes if they happen right in front of you.)
And then, once you got it running... installing it as a service should be a one-liner. Such as...
make-s6-service.sh our-sbcl-service /usr/bin/sbcl --load /home/simon/local/slime/start-swank-unix.lisp
... as in: just prepend something to your already-working command. Done. The service should be ready & configured. Or... at least ready for you to tweak further.
That should be it.
Here is an example implementation that does this. Partially from the depths of time, back when Codex was The Thing and you had to pretend you're writing comments at the beginning of your script instead of just telling GPT 4 what you want:
#!/bin/bash
# A bash script taking a service name as a parameter. It:
# - creates the directory /service/[servicename] if it doesn't exist
# - creates /service/[servicename]/run and makes it executable; puts in a single line with a shebang with /usr/bin/execlineb -P
# - creates /service/[servicename/log/run, which has something like
# #!/usr/bin/execlineb -P
# /usr/bin/s6-log T /var/log/[servicename]
# Written by Codex because hell yes
set -e
if [ $# -lt 1 ]; then
echo "Usage: $0
Of course... bash scripts might be ugly and there might be better solutions for this. The important part is the interface though: the part that this is almost as easy as running a UNIX command.
(... you might argue that it's silly that there is an arbitrary distinction of files that are persistent & processes that are not... we don't quite have to fix all the things ever right away though.)