From 80de03ccc994eb20120ef1271201a45b92aaa449 Mon Sep 17 00:00:00 2001 From: Nick Chambers Date: Sat, 19 Jun 2021 23:31:40 -0500 Subject: [PATCH] Add support for configuration files --- rowbot | 260 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 131 insertions(+), 129 deletions(-) diff --git a/rowbot b/rowbot index 3f2ab10..fe5c975 100755 --- a/rowbot +++ b/rowbot @@ -1,7 +1,36 @@ #!/usr/bin/env bash +### +# switch toggler +### + shopt -s nullglob dotglob extglob +### +# utilities +### + +parent() { + (( BASHPID == $$ )) +} + +has() { + hash "$1" 2>/dev/null +} + +die() { + local status=1 + + if (( $# > 1 )) && [[ $1 = -s ]]; then + status=$2 + shift + shift + fi + + error "$@" + exit "$status" +} + ### # logger ### @@ -13,7 +42,7 @@ declare -A levels=( log() { if [[ -v LEVEL ]] && (( levels[$level] <= levels[$LEVEL] )); then - printf "%s: $1\n" "${LEVEL^^}" "${@:2}" >&"$log" + printf "%s: $1\n" "${LEVEL^^}" "${@:2}" >&"$log_fd" fi } @@ -37,7 +66,7 @@ error() { # argument parser for parsing arguments ## -original_args=("$0" "$@") +original_args=( "$@" ) declare -A opts while (( $# )); do @@ -69,79 +98,118 @@ done # default config ## -server=${opts[server]:-irc.libera.chat} -tls=${opts[tls]:-no} +server=irc.libera.chat port=6667 tls=no client_cert= +nick=rowbot-dev ident=rowbot realname=rowbot chan= +trigger=\` fact_root=. reload=no level=info log_fd=1 log= +# shellcheck disable=SC2034 +sys_root=sysfacts owner=${USER:-uplime} dev=no + +### +# apply custom config +### + +for file do + if [[ -f $file ]]; then + # These files are provided dynamically at run-time. + # shellcheck disable=SC1090 + . "$file" # ha, dot file + else + die 'could not locate config file %s' "$file" + fi +done + +### +# apply command line options +### + +if [[ -v opts[tls] ]]; then + tls=${opts[tls]} +fi if [[ $tls = yes ]]; then - if ! hash socat 2>/dev/null; then - printf 'please install socat to use tls with rowbot.\n' >&2 - exit 1 - fi - - # false positive + # This is a false positive. # shellcheck disable=SC2102 - if [[ -v opts[client-cert] ]]; then + if ! has socat; then + die "please install socat to use tls with rowbot." + elif [[ -v opts[client-cert] ]]; then client_cert=${opts[client-cert]} fi - port=${opts[port]:-6697} -else - port=${opts[port]:-6667} + port=6697 fi -nick=${opts[nick]:-rowbot-dev} -ident=${opts[ident]:-rowbot} -realname=${opts[realname]:-rowbot} -chan=${opts[chan]:-} -trigger=${opts[trigger]:-\`} -fact_root=${opts[fact-root]:-.} -reload=${opts[reload]:-no} -dev=${opts[dev]:-no} -sysfacts_file=${opts[sysfacts]:-./sysfacts.txt} +config=( + server port nick ident realname chan trigger + fact_root reload dev level log owner sys_root + reload +) -if [[ -v USER ]]; then - owner=${opts[owner]:-"$USER"} -else - owner=${opts[owner]:-uplime} -fi +for opt in "${config[@]}"; do + declare -n config_var=$opt + cmd_arg=${opt//_/-} -level=${opts[log-level]:-info} + if [[ -v opts[$cmd_arg] ]]; then + # This variable is only used for assignment. + # shellcheck disable=SC2034 + config_var=${opts[$cmd_arg]} + fi +done -if [[ $reload = yes ]]; then - log=$LOG_FD -elif [[ ${opts[log]} ]]; then - exec {log}>"${opts[log]}" -else - log=1 +if [[ $log && $reload = no ]]; then + exec {log_fd}>"$log" fi ### -# utilities +# apply reloaded values +### + +if [[ $reload = yes ]]; then + reload_vars=( + nick ident host level log log_fd alarm_pid tls_pid in_sock + out_sock sock_dir sys_root fact_root dev trigger registered + keep_trying desired_nick + ) + + for var in "${reload_vars[@]}"; do + env_var=${var^^} + declare -n reload_var=$var + + if [[ -v $env_var ]]; then + # This variable is only used for assignment. + # shellcheck disable=SC2034 + reload_var=${!env_var} + fi + done +fi + +### +# process management ### cleanup() { - debug "cleaning up rowbot" + debug "reaping rowbot's children" - if [[ -v tls_pid ]]; then - kill "$tls_pid" - rm -rf "$sock_dir" + if [[ -v alarm_pid ]]; then + debug "killing alarm process (%d) from %d" "$alarm_pid" "$BASHPID" + kill "$alarm_pid" fi - if [[ -v ping_pid ]]; then - debug "cleaning up alarm in %d" "$BASHPID" - kill "$ping_pid" + if [[ -v tls_pid ]]; then + debug "killing tls process (%d) from %d" "$tls_pid" "$BASHPID" + kill "$tls_pid" + rm -rf "$sock_dir" fi if [[ -v tls_pid || $tls = no ]]; then exec {in_sock}>&- {out_sock}>&- - if (( log != 1 )); then - exec {log}>&- + if (( log_fd != 1 )); then + exec {log_fd}>&- fi fi } -if (( BASHPID == $$ )); then +if parent; then trap cleanup EXIT fi @@ -156,37 +224,6 @@ alarm-handler() { trap alarm-handler ALRM -### -# reload code -### - -if [[ $reload = yes ]]; then - in_sock=$IN_SOCK out_sock=$OUT_SOCK - trigger=$TRIGGER dev=$DEV level=$LOG_LEVEL - registered=$REGISTERED - - debug "doing a reload. pid is %d" "$BASHPID" - - if [[ -v KEEP_TRYING ]]; then - keep_trying=$KEEP_TRYING desired_nick=$DESIRED - fi - - if [[ $tls = yes ]]; then - sock_dir=$SOCK_DIR - tls_pid=$TLS_PID - fi - - if [[ -v PING_PID ]]; then - ping_pid=$PING_PID - fi - - nick=$NICK ident=$IDENT - - if [[ -v HOST ]]; then - host=$HOST - fi -fi - ### # net code ### @@ -195,10 +232,9 @@ if [[ $reload = no && $tls = yes ]]; then sock_dir=$(mktemp -d) mkfifo "$sock_dir"/rb{in,out} - if [[ -v client_cert ]]; then + if [[ $client_cert ]]; then if [[ ! -f $client_cert ]]; then - error "client certificate not found: %s" "$client_cert" - exit 1 + die "client certificate not found: %s" "$client_cert" fi conn_args=OPENSSL:$server:$port,cert=$client_cert @@ -208,8 +244,8 @@ if [[ $reload = no && $tls = yes ]]; then socat "$conn_args" - <"$sock_dir"/rbin >"$sock_dir"/rbout & tls_pid=$! - debug "created tls connection (pid %d)" "$tls_pid" exec {out_sock}>"$sock_dir"/rbin {in_sock}<"$sock_dir"/rbout + debug "created tls connection (pid %d)" "$tls_pid" elif [[ $reload = no ]]; then exec {sock}<>/dev/tcp/"$server"/"$port" in_sock=$sock out_sock=$sock @@ -218,7 +254,7 @@ fi send() { local fmt - # As this is a printf wrapper, the format string is provided as an argument + # As this is a printf wrapper, the format string is provided as an argument. # shellcheck disable=SC2059 printf -v fmt "$1" "${@:2}" printf '%s\r\n' "$fmt" >&"$out_sock" @@ -306,7 +342,7 @@ on_001() { done } & - ping_pid=$! + alarm_pid=$! nick=${params[0]} registered=yes who "$nick" %%uht,42 @@ -330,11 +366,11 @@ on_005() { local param key value for param in "${params[@]:1:${#params[@]}-2}"; do - # This is a valid assignment, not a comparison + # This is a valid assignment, not a comparison. # shellcheck disable=SC1097 IFS== read -r key value <<< "$param" # While isupport is unused, it's still there in case later code wants to - # use it + # use it. # shellcheck disable=SC2034 isupport[$key]=$value debug "isupport: %s = %s" "$key" "$value" @@ -647,29 +683,18 @@ hook_post_PRIVMSG_control_panel() { privmsg "$to" "joined ${words[1]}" ;; reload) - export IN_SOCK=$in_sock OUT_SOCK=$out_sock LOG_FD=$log DEV=$dev - export RELOAD_TO=$to TRIGGER=$trigger LOG_LEVEL=$level - export NICK=$nick IDENT=$ident REGISTERED=$registered + reload_vars=( + nick ident host level log log_fd alarm_pid tls_pid in_sock + out_sock sock_dir sys_root fact_root dev trigger registered + keep_trying desired_nick to + ) - if [[ $keep_trying = yes ]]; then - export KEEP_TRYING=yes DESIRED=$desired_nick - fi - - if [[ -v host ]]; then - export HOST=$host - fi - - if [[ $tls = yes ]]; then - export SOCK_DIR=$sock_dir - export TLS_PID=$tls_pid - fi - - if [[ -v ping_pid ]]; then - export PING_PID=$ping_pid - fi + for env_var in "${reload_vars[@]}"; do + export "${env_var^^}"="${!env_var}" + done privmsg "$to" "reloading..." - exec "${original_args[@]}" --reload + exec "$0" --reload "${original_args[@]}" ;; level) level=${words[1]} @@ -713,29 +738,6 @@ hook_post_PRIVMSG_control_panel() { fi } -sysfacts=( ) - -if [[ -f $sysfacts_file ]]; then - mapfile -t sysfacts < "$sysfacts_file" -fi - -hook_post_PRIVMSG_sysfacts() { - local to=${params[0]} idx - - if [[ ${params[0]:0:1} != \# ]]; then - to=$from - fi - - if [[ ${words[0]} = "$trigger"sysfact ]]; then - if (( ${#sysfacts[@]} == 0 )); then - privmsg "$to" "sysfacts are not loaded :(" - else - (( idx = RANDOM % ${#sysfacts[@]} )) - privmsg "$to" "sysfact #$(( idx + 1 )): ${sysfacts[$idx]}" - fi - fi -} - hook_post_433_alternick() { if [[ -z $desired_nick && $registered = no ]]; then desired_nick=${params[1]} @@ -755,7 +757,7 @@ hook_post_NICK_alternick() { ### if [[ $reload = yes ]]; then - privmsg "$RELOAD_TO" done. + privmsg "$TO" done. else registered=no info "rowbot's pid is %d" "$BASHPID"