2021-07-01 19:13:31 +00:00
|
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
|
|
|
|
|
###
|
2021-07-04 08:35:11 +00:00
|
|
|
|
# feature switch toggling
|
2021-07-01 19:13:31 +00:00
|
|
|
|
###
|
|
|
|
|
|
2021-07-04 08:35:11 +00:00
|
|
|
|
shopt -s dotglob extglob lastpipe nullglob
|
2021-07-01 19:13:31 +00:00
|
|
|
|
|
2021-07-04 08:35:11 +00:00
|
|
|
|
###
|
2021-07-04 11:25:08 +00:00
|
|
|
|
# utility and helper functions
|
2021-07-04 08:35:11 +00:00
|
|
|
|
###
|
2021-07-01 19:13:31 +00:00
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
any_file() {
|
|
|
|
|
local files=( "${1:-.}"/* ) max idx
|
|
|
|
|
|
|
|
|
|
if (( ${#files[@]} > 1 )); then
|
|
|
|
|
(( max = ${#files[@]} - 1 ))
|
|
|
|
|
idx=$(random 0 "$max")
|
|
|
|
|
printf %s "${files[$idx]}"
|
|
|
|
|
fi
|
|
|
|
|
}
|
2021-07-01 19:13:31 +00:00
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
die() {
|
|
|
|
|
local fmt=$1
|
|
|
|
|
shift
|
|
|
|
|
# This is a wrapper around printf so the format string isn't known ahead of
|
|
|
|
|
# time.
|
|
|
|
|
# shellcheck disable=SC2059
|
|
|
|
|
printf "$fmt\n" "$@" >&2
|
|
|
|
|
exit "${STATUS:-42}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get_option() {
|
|
|
|
|
if (( $# != 2 )); then
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local var_name=${1//-/_}
|
|
|
|
|
local env_var=${var_name^^}
|
|
|
|
|
|
|
|
|
|
if [[ -v $env_var ]]; then
|
|
|
|
|
config[$1]=${!env_var}
|
|
|
|
|
elif [[ -v opts[$1] ]]; then
|
|
|
|
|
config[$1]=${opts[$1]}
|
|
|
|
|
elif [[ -v $var_name ]]; then
|
|
|
|
|
config[$1]=${!var_name}
|
|
|
|
|
else
|
|
|
|
|
config[$1]=$2
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
has() {
|
|
|
|
|
if (( $# )); then
|
|
|
|
|
hash "$1" 2>/dev/null
|
|
|
|
|
else
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
is_parent() {
|
|
|
|
|
(( BASHPID == $$ ))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
is_reloaded() {
|
|
|
|
|
[[ $RELOADED = yes ]] || (( LORE_LIVES > 1 ))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
random() {
|
|
|
|
|
local min=$1 max=$2
|
|
|
|
|
(( (RANDOM % max) + min ))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
run_callbacks() {
|
|
|
|
|
if (( ! $# )); then
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local filter=$1
|
|
|
|
|
shift
|
|
|
|
|
|
|
|
|
|
while IFS= read -r; do
|
|
|
|
|
"$REPLY" "$@"
|
|
|
|
|
done < <(compgen -A function "$filter")
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
###
|
|
|
|
|
# configure rowbot's environment
|
|
|
|
|
###
|
2021-07-01 19:13:31 +00:00
|
|
|
|
|
2021-07-04 08:35:11 +00:00
|
|
|
|
# parse command line arguments
|
2021-07-01 19:13:31 +00:00
|
|
|
|
|
|
|
|
|
declare -A opts
|
2021-07-04 08:35:11 +00:00
|
|
|
|
cmd_line=( "${@:0}" )
|
2021-07-01 19:13:31 +00:00
|
|
|
|
|
|
|
|
|
while (( $# )); do
|
|
|
|
|
case $1 in
|
|
|
|
|
--*=*)
|
|
|
|
|
key=${1#--} key=${key%%=*}
|
|
|
|
|
opts[$key]=${1#--*=}
|
|
|
|
|
;;
|
|
|
|
|
--no-*)
|
|
|
|
|
key=${1#--no-}
|
|
|
|
|
opts[$key]=no
|
|
|
|
|
;;
|
|
|
|
|
--)
|
|
|
|
|
shift
|
|
|
|
|
break
|
|
|
|
|
;;
|
|
|
|
|
--*)
|
|
|
|
|
key=${1#--}
|
|
|
|
|
opts[$key]=yes
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
break
|
|
|
|
|
esac
|
|
|
|
|
|
|
|
|
|
shift
|
|
|
|
|
done
|
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
# load custom configuration files
|
2021-07-01 19:13:31 +00:00
|
|
|
|
|
|
|
|
|
for file do
|
2021-07-04 09:57:52 +00:00
|
|
|
|
if [[ -f $file ]]; then
|
|
|
|
|
# These files (if any) are provided dynamically at run-time.
|
|
|
|
|
# shellcheck disable=SC1090
|
|
|
|
|
. "$file" # ha, dot file
|
|
|
|
|
else
|
|
|
|
|
die "could not locate config file %s" "$file"
|
|
|
|
|
fi
|
2021-07-01 19:13:31 +00:00
|
|
|
|
done
|
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
# cleanup
|
2021-07-04 08:35:11 +00:00
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
unset key file
|
2021-07-01 19:13:31 +00:00
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
###
|
|
|
|
|
# load default config
|
|
|
|
|
###
|
2021-07-01 19:13:31 +00:00
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
declare -A config
|
2021-07-01 19:13:31 +00:00
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
# irc registration settings
|
2021-07-04 09:57:52 +00:00
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
get_option nick rowbot-dev
|
|
|
|
|
get_option ident rowbot
|
|
|
|
|
get_option realname rowbot
|
|
|
|
|
get_option chan ""
|
2021-07-01 19:13:31 +00:00
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
# bot control settings
|
|
|
|
|
|
|
|
|
|
get_option owner "${USER:-uplime}"
|
|
|
|
|
get_option trigger \`
|
|
|
|
|
get_option dev yes
|
2021-07-04 09:57:52 +00:00
|
|
|
|
|
2021-07-05 23:14:23 +00:00
|
|
|
|
###
|
|
|
|
|
# bootup sequence
|
|
|
|
|
###
|
|
|
|
|
|
|
|
|
|
on_first_001_bootup() {
|
|
|
|
|
log_info "rowbot's pid is %d" "$$"
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-04 09:57:52 +00:00
|
|
|
|
###
|
2021-07-04 11:25:08 +00:00
|
|
|
|
# logger
|
2021-07-04 09:57:52 +00:00
|
|
|
|
###
|
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
log() {
|
|
|
|
|
if [[ -v LOG_LEVEL ]] && (( log_levels[$log_level] <= log_levels[$LOG_LEVEL] )); then
|
|
|
|
|
printf "%s: $1\n" "${LOG_LEVEL^^}" "${@:2}" >&"$log_fd"
|
2021-07-04 10:13:57 +00:00
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
log_debug() {
|
|
|
|
|
LOG_LEVEL=debug log "$@"
|
2021-07-04 09:57:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
log_info() {
|
|
|
|
|
LOG_LEVEL=info log "$@"
|
2021-07-04 09:59:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
log_warn() {
|
|
|
|
|
LOG_LEVEL=warn log "$@"
|
2021-07-04 09:59:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
log_error() {
|
|
|
|
|
LOG_LEVEL=error log "$@"
|
2021-07-04 09:59:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
log_has_level() {
|
|
|
|
|
local level
|
2021-07-04 09:57:52 +00:00
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
for level in "${!log_levels[@]}"; do
|
|
|
|
|
if [[ ${1,,} = "$level" ]]; then
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
return 1
|
2021-07-04 10:13:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
on_init_001_log() {
|
|
|
|
|
declare -gA log_levels=( [debug]=1 [info]=2 [warn]=3 [error]=4 )
|
|
|
|
|
get_option log-level info
|
|
|
|
|
|
|
|
|
|
if ! log_has_level "${config[log-level]}"; then
|
|
|
|
|
die "%s is not a valid logging level" "${config[log-level]}"
|
2021-07-04 09:57:52 +00:00
|
|
|
|
fi
|
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
get_option log ""
|
|
|
|
|
get_option overwrite no
|
|
|
|
|
log_level=${config[log-level]}
|
2021-07-04 09:57:52 +00:00
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
if [[ ${config[log]} ]]; then
|
|
|
|
|
if [[ ${config[overwrite]} = yes ]]; then
|
|
|
|
|
exec {log_fd}>"${config[log]}"
|
|
|
|
|
else
|
|
|
|
|
exec {log_fd}>>"${config[log]}"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
log_fd=1
|
|
|
|
|
fi
|
|
|
|
|
}
|
2021-07-04 09:57:52 +00:00
|
|
|
|
|
2021-07-05 23:15:37 +00:00
|
|
|
|
on_exit_zzz_log() {
|
2021-07-04 11:25:08 +00:00
|
|
|
|
if [[ -v log_fd ]] && (( log_fd != 1 )); then
|
|
|
|
|
exec {log_fd}>&-
|
|
|
|
|
fi
|
2021-07-04 09:57:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-07-05 23:14:23 +00:00
|
|
|
|
###
|
|
|
|
|
# net code
|
|
|
|
|
###
|
|
|
|
|
|
|
|
|
|
net_recv() {
|
|
|
|
|
declare -n sock_line=$1
|
|
|
|
|
IFS= read -r "$1" <&"$in_sock"
|
|
|
|
|
sock_line=${sock_line%$'\r'}
|
|
|
|
|
log_debug "received line: %s" "$sock_line"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
net_send() {
|
|
|
|
|
local fmt
|
|
|
|
|
# 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"
|
|
|
|
|
log_debug "sending line: %s" "$fmt"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
on_init_002_net() {
|
|
|
|
|
get_option server irc.libera.chat
|
|
|
|
|
get_option tls no
|
|
|
|
|
|
|
|
|
|
if [[ ${config[tls]} = no ]]; then
|
|
|
|
|
get_option port 6667
|
|
|
|
|
else
|
|
|
|
|
get_option client-cert ""
|
|
|
|
|
get_option port 6697
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
on_first_002_net() {
|
|
|
|
|
local irc_sock conn_args
|
|
|
|
|
|
|
|
|
|
if [[ ${config[tls]} = no ]]; then
|
|
|
|
|
log_info "rowbot is connecting to irc://%s:%s" "${config[server]}" "${config[port]}"
|
|
|
|
|
exec {irc_sock}<>/dev/tcp/"${config[server]}"/"${config[port]}"
|
|
|
|
|
in_sock=$irc_sock out_sock=$irc_sock
|
|
|
|
|
else
|
|
|
|
|
if ! has socat; then
|
|
|
|
|
die "please install socat to use tls with rowbot."
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
log_info "rowbot is connecting to ircs://%s:%s" "${config[server]}" "${config[port]}"
|
|
|
|
|
sock_dir=$(mktemp -d)
|
|
|
|
|
log_debug "socket directory is %s" "$sock_dir"
|
|
|
|
|
mkfifo "$sock_dir"/rowbot-{in,out}.sock
|
|
|
|
|
|
|
|
|
|
# This is a false positive
|
|
|
|
|
# shellcheck disable=SC2102
|
|
|
|
|
if [[ -v config[client-cert] ]]; then
|
|
|
|
|
if [[ ! -f ${config[client-cert]} ]]; then
|
|
|
|
|
die "client certificate not found: %s" "${config[client-cert]}"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
conn_args=OPENSSL:${config[server]}:${config[port]},cert=${config[client-cert]}
|
|
|
|
|
else
|
|
|
|
|
conn_args=OPENSSL:${config[server]}:${config[port]}
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
socat "$conn_args" - <"$sock_dir"/rowbot-in.sock >"$sock_dir"/rowbot-out.sock &
|
|
|
|
|
tls_pid=$!
|
|
|
|
|
exec {out_sock}>"$sock_dir"/rowbot-out.sock {in_sock}<"$sock_dir"/rowbot-in.sock
|
|
|
|
|
log_debug "process %d is handling tls" "$tls_pid"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
|
###
|
|
|
|
|
# cleanup
|
|
|
|
|
###
|
|
|
|
|
|
|
|
|
|
cleanup() {
|
2021-07-05 23:14:23 +00:00
|
|
|
|
log_info "There’s a lot of beauty in ordinary things. Isn’t that kind of the point?"
|
2021-07-04 11:25:08 +00:00
|
|
|
|
run_callbacks on_exit_
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trap cleanup EXIT
|
|
|
|
|
|
2021-07-04 09:57:52 +00:00
|
|
|
|
###
|
|
|
|
|
# live code reloader
|
|
|
|
|
###
|
|
|
|
|
|
2021-07-05 23:14:23 +00:00
|
|
|
|
reload_config() {
|
2021-07-04 11:25:08 +00:00
|
|
|
|
local setting setting_name
|
|
|
|
|
run_callbacks on_before_
|
|
|
|
|
|
|
|
|
|
for setting in "${!config[@]}"; do
|
|
|
|
|
setting_name=${setting//-/_}
|
|
|
|
|
export "${setting_name^^}"="${config[$setting]}"
|
|
|
|
|
done
|
|
|
|
|
|
2021-07-04 09:57:52 +00:00
|
|
|
|
exec "${cmd_line[@]}"
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-05 23:14:23 +00:00
|
|
|
|
reload_hup() {
|
2021-07-04 11:25:08 +00:00
|
|
|
|
log_info "received reload signal (HUP)"
|
2021-07-05 23:14:23 +00:00
|
|
|
|
reload_config
|
2021-07-04 09:57:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-07-05 23:14:23 +00:00
|
|
|
|
trap reload_hup HUP
|
2021-07-04 09:57:52 +00:00
|
|
|
|
|
2021-07-01 19:13:31 +00:00
|
|
|
|
###
|
2021-07-04 08:35:11 +00:00
|
|
|
|
# initialization sequence
|
2021-07-01 19:13:31 +00:00
|
|
|
|
###
|
2021-07-04 09:57:52 +00:00
|
|
|
|
|
|
|
|
|
run_callbacks on_init_
|
|
|
|
|
|
|
|
|
|
if is_reloaded; then
|
2021-07-04 11:25:08 +00:00
|
|
|
|
run_callbacks on_after_
|
2021-07-04 09:57:52 +00:00
|
|
|
|
else
|
|
|
|
|
run_callbacks on_first_
|
|
|
|
|
fi
|