Is systemd bloated ? A pubsub server in 200 lines of code in bash talking on stdout/stdin that does not require systemd

Two days ago I was reading this about a « new vendor locking » made in lehnart pottering (systemd) and it gave me the will to see how much the assertion of this Laurent Bercot was true.
The new vendor locking is about an old technique used in inetd.
It took me one day to decide what to code, and I began today using uscpi toolsuite.

To be honest, I am a little bit of a fanboy of DJ Bernstein. He is from the school of keep it simple stupid and ROCK HARD idiot proof. And ... I am an idiot of my kind. I just want to do server logic by talking ot stdin/stdout and I am pretty glad to see there exists a correct tool to connect stdion/stdout to a TCP socket.

I decided to add injury to the team of the « I need the latest shibazam* team » and to code in bash.

* shibazam being any of « memory safe », « cryptographically proven », « portable » ...

Doing a pubsub server in bash



a publish/subscribe server is a very simple concept : you have channels on which you write and concurrent readers who can read. It was first used for tickers of stock exchange.

A channel can be bijected to a file, and as long as you handle concurrent writing with an exclusive lock, nothing wrong can happen.

A TCP server is basically a REP/REQ Pattern, while true, you listen to what the user says and you answer and come back to listening.

The perfect job for a DISPATCH TABLE I use to bashize my makefile, hence, the code for which I could basically steal my own code.

I don't know what to say, because it actually worked as a skeleton (saying what it would do but actually doing nothing) below 2 hours flat after the beginning of the coding so I dare say using bash was neat and fun.

Sometimes, I have the feeling we over-engineer our solutions.

Securing the server ?



DJB gives some nice input out of the box with the possibility to add ingres and outgres network rules in a portable way (CDB). And, if you are (rightly) paranoid, and want to deploy it on the internet, I strongly advise to use the beauty of TCP/IP tunneling. Stunnel will add both private key authentication and ciphering without having to roll your own cryptography by tunnelling your clear text protocol in an SSL ciphered IP socket.

I really have the feeling modern days is about over-complexifying design to make money and justify our bullshit jobs. I know by having tried that this kind of solutions don't make it to production line because « it looks to simple and don't give warranties of being sure enough ».

Annexe

The code with a tad of explanation on how to use it in the "?)" case that took me 6 hours to code, SLOWLY. VERY SLOWLY.
#!/usr/bin/env bash
# https://gist.github.com/jul/e9dcfdfb2490f6df3930dfe8ee29ead1
# WTFPL2.0 do WTF you want with the code except claiming paternity
# require ucspi-tcp http://cr.yp.to/ucspi-tcp/tcpserver.html
# server launched with tcpserver localhost 1234 ./pubsub.sh
# client launched with tcpclient localhost 1234 ./psclient.sh # https://gist.github.com/jul/e9dcfdfb2490f6df3930dfe8ee29ead1#file-psclient-sh
# TODO unsub
# TODO trap SIHGUP to make file rotations (avoiding to have to stop/start server to do so)
set -e
declare -a action;
declare -a ppids;
#set -x
DEBUG=1
echo "BEGINNING!"
RD='\e[31m'
GB='\e[33m'
BL='\e[34m'
GR='\e[0;90m\e[1;47m'
RZ='\e[0m'
#delcr() { echo "$*" | sed -e 's/\n\r$/\n/'; }
atomic_create () {(set -o noclobber;>"$1") &>/dev/null; }
d() {
(( $DEBUG )) && echo -e "# [D]:$(date +"%H:%M:%S"):$GR $* $RZ";
}
push() {
local stack
declare -a stack
stack=( "$@" )
for ((i=${#@}; i--; i)); do
action=( "${stack[$i]}" "${action[@]}" );
done
}
trim () {
echo "$@" | delcr
}
check () { echo "$@" | perl -ane '/^([a-z0-9]+)$/ or die "[\e[31mE\e[0m] illicit characters detected only [a-z0-9] authorized"' 2>&1 ;}
w() {
echo -e "# [${BL}W${RZ}] $@" ;
}
i() {
echo -e "# [${GB}I${RZ}] $@" ;
}
[ -d out ] || ( mkdir out; chmod 2060 out )
e() {
local EX="$1"
shift
echo -e "# [${BL}EXITING${RZ}] ${*}"
exit $EX
}
pub() {
local channel=$1;
shift
check $channel || (w illicit channel name $channel ; return )
[ -f out/$channel ] || ( i "creating <$channel>" )
if [ -f out/$channel.lock ]; then
w lock taken;
return
fi
atomic_create out/$channel.lock
echo -n "$USER on $channel at $(date +'%H:%M:%S'): " >> out/$channel
while (( ${#action} )); do
echo -n $( echo ${action[0]} | delcr ) >> out/$channel
echo -n " " >> out/$channel
action=("${action[@]:1}")
done
echo >> out/$channel
echo
rm out/$channel.lock;
}
exit_script() {
d "killing all subs"
for pid in "${ppids[@]}"; do
kill $pid;
done
e 1 "byebye"
trap - SIGINT SIGTERM SIGPIPE # clear the trap
kill -- -$$ # Sends SIGTERM to child/sub processes
}
trap exit_script SIGINT SIGTERM SIGPIPE
dispatch_action() {
while [[ ${#action} -gt 0 ]]; do
# pop in shell :D
act=${action[0]}
action=("${action[@]:1}")
# end of pop
case $act in
pub)
channel=$( trim ${action[0]} )
action=("${action[@]:1}")
pub $channel
;;
sub)
argument=$( trim ${action[0]} )
d "subscribing <$argument>"
action=("${action[@]:1}")
if [ ! -f out/$argument ]; then
w "<$argument> doesn't exist. Publish on channel to create ot"
break
fi
if check $argument; then
tail -f out/$argument &
ppids+=( "$!" );
fi
;;
bye)
exit_script
;;
\")
d publishing in general
push "pub" "general"
;;
\?)
cat <<EOF
<channel> must be in form [a-z0-9]+
?
this help
pub <channel> [line of text to add in channel]
publish on channel the text
create channel if inexistant silently
sub <channel>
suscribe to channel
bye
exit cleanly and politely
"
publish on general channel to which you are subscribed at startup
EOF
;;
*)
i "unrecognized action <$act>. Try ? for help"
break
;;
esac
done
}
push '"' "new subscriber entering"
dispatch_action
push "sub" "general"
dispatch_action
while [ 1 ]; do
[ -z "$SILENT" ] || echo -n "ready> "
[ -z "$SILENT" ] && read -s -r -a action || read -a action
action=$( echo $action | delcr )
dispatch_action
done
view raw pubsub.sh hosted with ❤ by GitHub

No comments: