Add support for configuration files

This commit is contained in:
Nick Chambers 2021-06-19 23:31:40 -05:00
parent ff989adc36
commit 80de03ccc9
1 changed files with 131 additions and 129 deletions

260
rowbot
View File

@ -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"