templating, rate-limiting, etc.
This commit is contained in:
@@ -2,10 +2,16 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
BIN_SRC="./whisper"
|
BIN_SRC="./whisper"
|
||||||
BIN_DST="/usr/bin/whisper"
|
BIN_DIR="$HOME/.local/bin"
|
||||||
CONF_DIR="/etc/whisper"
|
BIN_DST="$BIN_DIR/whisper"
|
||||||
|
CONF_DIR="$HOME/.config/whisper"
|
||||||
CONF_DST="$CONF_DIR/.env"
|
CONF_DST="$CONF_DIR/.env"
|
||||||
CONF_SRC="./example.env"
|
CONF_SRC="./example.env"
|
||||||
|
TMPL_SRC_DIR="./templates"
|
||||||
|
TMPL_DST_DIR="$CONF_DIR/templates"
|
||||||
|
FISH_BIN="/opt/homebrew/bin/fish"
|
||||||
|
FISH_FUNC_DIR="$HOME/.config/fish/functions"
|
||||||
|
FISH_FUNC="$FISH_FUNC_DIR/whisper.fish"
|
||||||
|
|
||||||
die() {
|
die() {
|
||||||
printf '%s\n' "$1" >&2
|
printf '%s\n' "$1" >&2
|
||||||
@@ -20,10 +26,45 @@ usage() {
|
|||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Append PATH export to an rc file if BIN_DIR isn't already referenced there
|
||||||
|
patch_rc() {
|
||||||
|
_rc="$1"
|
||||||
|
[ -f "$_rc" ] || return 0
|
||||||
|
grep -qF "$BIN_DIR" "$_rc" && return 0
|
||||||
|
printf '\nexport PATH="%s:$PATH"\n' "$BIN_DIR" >> "$_rc"
|
||||||
|
printf ' - Added %s to PATH in %s\n' "$BIN_DIR" "$_rc"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_in_path() {
|
||||||
|
patch_rc "$HOME/.bashrc"
|
||||||
|
patch_rc "$HOME/.bash_profile"
|
||||||
|
patch_rc "$HOME/.zshrc"
|
||||||
|
patch_rc "$HOME/.zprofile"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_fish_function() {
|
||||||
|
mkdir -p "$FISH_FUNC_DIR"
|
||||||
|
printf 'function whisper --description "Send messages via transport (telegram)"\n' > "$FISH_FUNC"
|
||||||
|
printf ' %s $argv\n' "$BIN_DST" >> "$FISH_FUNC"
|
||||||
|
printf 'end\n' >> "$FISH_FUNC"
|
||||||
|
printf ' - Created fish function at %s\n' "$FISH_FUNC"
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_fish_function() {
|
||||||
|
if [ -f "$FISH_FUNC" ]; then
|
||||||
|
rm -f "$FISH_FUNC"
|
||||||
|
printf ' - Removed %s\n' "$FISH_FUNC"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
do_install() {
|
do_install() {
|
||||||
printf 'This will:\n'
|
printf 'This will:\n'
|
||||||
printf ' - Copy whisper to %s\n' "$BIN_DST"
|
printf ' - Copy whisper to %s\n' "$BIN_DST"
|
||||||
printf ' - Create %s/ with config\n\n' "$CONF_DIR"
|
printf ' - Create %s/ with config\n' "$CONF_DIR"
|
||||||
|
printf ' - Patch ~/.bashrc, ~/.bash_profile, ~/.zshrc, ~/.zprofile if needed\n'
|
||||||
|
[ -x "$FISH_BIN" ] && printf ' - Create fish function at %s\n' "$FISH_FUNC"
|
||||||
|
printf ' - Seed %s/ with example templates\n' "$TMPL_DST_DIR"
|
||||||
|
printf '\n'
|
||||||
|
|
||||||
printf 'Proceed? [Y/n] '
|
printf 'Proceed? [Y/n] '
|
||||||
read -r answer
|
read -r answer
|
||||||
@@ -34,20 +75,35 @@ do_install() {
|
|||||||
[ -f "$BIN_SRC" ] || die "Cannot find $BIN_SRC in current directory"
|
[ -f "$BIN_SRC" ] || die "Cannot find $BIN_SRC in current directory"
|
||||||
[ -f "$CONF_SRC" ] || die "Cannot find $CONF_SRC in current directory"
|
[ -f "$CONF_SRC" ] || die "Cannot find $CONF_SRC in current directory"
|
||||||
|
|
||||||
|
mkdir -p "$BIN_DIR"
|
||||||
cp "$BIN_SRC" "$BIN_DST"
|
cp "$BIN_SRC" "$BIN_DST"
|
||||||
chown root:root "$BIN_DST"
|
|
||||||
chmod 755 "$BIN_DST"
|
chmod 755 "$BIN_DST"
|
||||||
|
|
||||||
mkdir -p "$CONF_DIR"
|
mkdir -p "$CONF_DIR"
|
||||||
|
|
||||||
if [ ! -f "$CONF_DST" ]; then
|
if [ ! -f "$CONF_DST" ]; then
|
||||||
cp "$CONF_SRC" "$CONF_DST"
|
cp "$CONF_SRC" "$CONF_DST"
|
||||||
|
chmod 600 "$CONF_DST"
|
||||||
else
|
else
|
||||||
printf 'Keeping existing %s\n' "$CONF_DST"
|
printf ' - Keeping existing %s\n' "$CONF_DST"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
chown root:root "$CONF_DST"
|
mkdir -p "$TMPL_DST_DIR"
|
||||||
chmod 644 "$CONF_DST"
|
if [ -d "$TMPL_SRC_DIR" ]; then
|
||||||
|
for _tmpl in "$TMPL_SRC_DIR"/*; do
|
||||||
|
[ -f "$_tmpl" ] || continue
|
||||||
|
_basename=$(basename "$_tmpl")
|
||||||
|
if [ ! -f "$TMPL_DST_DIR/$_basename" ]; then
|
||||||
|
cp "$_tmpl" "$TMPL_DST_DIR/$_basename"
|
||||||
|
printf ' - Installed template: %s\n' "$_basename"
|
||||||
|
else
|
||||||
|
printf ' - Keeping existing template: %s\n' "$_basename"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
ensure_in_path
|
||||||
|
|
||||||
|
[ -x "$FISH_BIN" ] && install_fish_function
|
||||||
|
|
||||||
printf '\nInstalled. Edit %s and set your values.\n' "$CONF_DST"
|
printf '\nInstalled. Edit %s and set your values.\n' "$CONF_DST"
|
||||||
}
|
}
|
||||||
@@ -55,7 +111,9 @@ do_install() {
|
|||||||
do_uninstall() {
|
do_uninstall() {
|
||||||
printf 'This will remove:\n'
|
printf 'This will remove:\n'
|
||||||
printf ' - %s\n' "$BIN_DST"
|
printf ' - %s\n' "$BIN_DST"
|
||||||
printf ' - %s/\n\n' "$CONF_DIR"
|
[ -f "$FISH_FUNC" ] && printf ' - %s\n' "$FISH_FUNC"
|
||||||
|
[ -d "$CONF_DIR" ] && printf ' - %s/ (env files confirmed individually)\n' "$CONF_DIR"
|
||||||
|
printf '\n'
|
||||||
|
|
||||||
printf 'Proceed? [y/N] '
|
printf 'Proceed? [y/N] '
|
||||||
read -r answer
|
read -r answer
|
||||||
@@ -65,14 +123,38 @@ do_uninstall() {
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
rm -f "$BIN_DST"
|
rm -f "$BIN_DST"
|
||||||
rm -rf "$CONF_DIR"
|
remove_fish_function
|
||||||
|
|
||||||
|
if [ -d "$CONF_DIR" ]; then
|
||||||
|
for _f in "$CONF_DIR"/.env "$CONF_DIR"/*.env; do
|
||||||
|
[ -f "$_f" ] || continue
|
||||||
|
printf 'Delete %s? [y/N] ' "$_f"
|
||||||
|
read -r _ans
|
||||||
|
case "$_ans" in
|
||||||
|
[yY]) rm -f "$_f"; printf ' - Removed %s\n' "$_f" ;;
|
||||||
|
*) printf ' - Kept %s\n' "$_f" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
if [ -d "$TMPL_DST_DIR" ]; then
|
||||||
|
printf 'Delete templates directory %s/? [y/N] ' "$TMPL_DST_DIR"
|
||||||
|
read -r _ans
|
||||||
|
case "$_ans" in
|
||||||
|
[yY]) rm -rf "$TMPL_DST_DIR"; printf ' - Removed %s/\n' "$TMPL_DST_DIR" ;;
|
||||||
|
*) printf ' - Kept %s/\n' "$TMPL_DST_DIR" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
# Remove the directory only if it is now empty
|
||||||
|
if [ -z "$(ls -A "$CONF_DIR" 2>/dev/null)" ]; then
|
||||||
|
rmdir "$CONF_DIR"
|
||||||
|
else
|
||||||
|
printf ' - Kept %s/ (not empty)\n' "$CONF_DIR"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
printf 'Done.\n'
|
printf 'Done.\n'
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- main ---
|
# --- main ---
|
||||||
[ "$(id -u)" -eq 0 ] || die "Must be run as root"
|
|
||||||
|
|
||||||
ACTION="install"
|
ACTION="install"
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
|||||||
4
whisper/templates/alert
Normal file
4
whisper/templates/alert
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<b>Alert: {1}</b>
|
||||||
|
Host: {2}
|
||||||
|
|
||||||
|
{3}
|
||||||
4
whisper/templates/deploy
Normal file
4
whisper/templates/deploy
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<b>Deployment</b>
|
||||||
|
Service: {1}
|
||||||
|
Version: {2}
|
||||||
|
Status: {3}
|
||||||
5
whisper/templates/report
Normal file
5
whisper/templates/report
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{1} finished with exit-code {2}. Command: {3}
|
||||||
|
|
||||||
|
{4}
|
||||||
|
---
|
||||||
|
sent by whisper
|
||||||
157
whisper/whisper
157
whisper/whisper
@@ -1,5 +1,14 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# whisper - send messages via transport (telegram)
|
# whisper - send messages via transport (telegram)
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 success (message sent, or --help)
|
||||||
|
# 1 usage error (bad/missing arguments, unknown option, unknown transport)
|
||||||
|
# 2 dependency error (required external command not found)
|
||||||
|
# 3 config error (required environment variable not set)
|
||||||
|
# 4 send error (transport request failed or API rejected the message)
|
||||||
|
# 5 rate limited (called too soon; retry after RATE_LIMIT_MS ms)
|
||||||
|
# 6 template error (template not found, missing params, or file read failure)
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
die() {
|
die() {
|
||||||
@@ -8,36 +17,148 @@ die() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
printf 'Usage: whisper -m MESSAGE [-t TRANSPORT]\n\n'
|
printf 'Usage: whisper -m MESSAGE [--via TRANSPORT]\n'
|
||||||
printf ' -m, --message Message to send (required)\n'
|
printf ' whisper -t TEMPLATE [-p VALUE ...] [--via TRANSPORT]\n\n'
|
||||||
printf ' -t, --transport Transport to use (default: telegram)\n'
|
printf ' -m, --message Message to send\n'
|
||||||
|
printf ' -t, --template Use a named template from ~/.config/whisper/templates/\n'
|
||||||
|
printf ' -p Positional params for template ({1}, {2}, ...)\n'
|
||||||
|
printf ' Prefix with @ to inject file contents\n'
|
||||||
|
printf ' --via, --transport, -T\n'
|
||||||
|
printf ' Transport to use (default: telegram)\n'
|
||||||
printf ' -h, --help Show this help\n'
|
printf ' -h, --help Show this help\n'
|
||||||
|
printf '\nExamples:\n'
|
||||||
|
printf ' whisper -m "Server is down"\n'
|
||||||
|
printf ' whisper -t deploy -p "myapp" "v1.2.3" "success"\n'
|
||||||
|
printf ' whisper -t alert -p "disk full" "web-01" @/tmp/df-output.txt\n'
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- template engine ---
|
||||||
|
TMPL_DIR="$HOME/.config/whisper/templates"
|
||||||
|
|
||||||
|
resolve_template() {
|
||||||
|
_tmpl_path="$TMPL_DIR/$TEMPLATE"
|
||||||
|
[ -f "$_tmpl_path" ] || die "template not found: $TEMPLATE (looked in $TMPL_DIR/)" 6
|
||||||
|
|
||||||
|
_content=$(cat "$_tmpl_path")
|
||||||
|
|
||||||
|
# Resolve @file references in positional params
|
||||||
|
_i=1
|
||||||
|
while [ "$_i" -le "$PARAM_COUNT" ]; do
|
||||||
|
eval "_val=\$_P${_i}"
|
||||||
|
case "$_val" in
|
||||||
|
@*)
|
||||||
|
_fpath="${_val#@}"
|
||||||
|
[ -f "$_fpath" ] || die "file not found for param ${_i}: $_fpath" 6
|
||||||
|
eval "_P${_i}=\$(cat \"\$_fpath\")"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
_i=$((_i + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
# Substitute {N} placeholders (awk handles special chars safely)
|
||||||
|
_i=1
|
||||||
|
while [ "$_i" -le "$PARAM_COUNT" ]; do
|
||||||
|
eval "_val=\$_P${_i}"
|
||||||
|
_placeholder="{${_i}}"
|
||||||
|
_content=$(printf '%s\n' "$_content" | _WHISPER_REP="$_val" awk \
|
||||||
|
-v pat="$_placeholder" \
|
||||||
|
'BEGIN {
|
||||||
|
rep = ENVIRON["_WHISPER_REP"]
|
||||||
|
# Escape braces so pat is treated as literal, not regex quantifier
|
||||||
|
gsub(/\{/, "\\{", pat)
|
||||||
|
gsub(/\}/, "\\}", pat)
|
||||||
|
gsub(/\\/, "\\\\", rep)
|
||||||
|
gsub(/&/, "\\&", rep)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
gsub(pat, rep)
|
||||||
|
printf "%s%s", sep, $0
|
||||||
|
sep = "\n"
|
||||||
|
}')
|
||||||
|
_i=$((_i + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check for unfilled placeholders
|
||||||
|
case "$_content" in
|
||||||
|
*"{"[0-9]*"}"*)
|
||||||
|
die "template '$TEMPLATE' has unfilled placeholders (got $PARAM_COUNT param(s))" 6 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
MESSAGE="$_content"
|
||||||
|
}
|
||||||
|
|
||||||
# --- config loading ---
|
# --- config loading ---
|
||||||
# source .env files if they exist (later file wins, env vars win over both)
|
# source .env files if they exist (later file wins, env vars win over both)
|
||||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||||
for _envfile in /etc/whisper/.env "$SCRIPT_DIR/.env"; do
|
for _envfile in /etc/whisper/.env "$HOME/.config/whisper/.env" "$SCRIPT_DIR/.env"; do
|
||||||
[ -f "$_envfile" ] && . "$_envfile"
|
[ -f "$_envfile" ] && . "$_envfile"
|
||||||
done
|
done
|
||||||
unset _envfile
|
unset _envfile
|
||||||
|
|
||||||
# --- dependency check ---
|
# --- dependency check ---
|
||||||
command -v curl >/dev/null 2>&1 || die "curl is required but not found" 1
|
command -v curl >/dev/null 2>&1 || die "curl is required but not found" 2
|
||||||
|
|
||||||
|
# --- rate limiter ---
|
||||||
|
RATE_LIMIT_MS="${RATE_LIMIT_MS:-1000}"
|
||||||
|
_RATE_STATE_FILE="$HOME/.config/whisper/.last_send"
|
||||||
|
|
||||||
|
_now_ms() {
|
||||||
|
# GNU date supports %s%3N; macOS date does not (%3N prints literally)
|
||||||
|
_t=$(date +%s%3N 2>/dev/null)
|
||||||
|
case "$_t" in
|
||||||
|
*N) # macOS fallback via python3
|
||||||
|
python3 -c "import time; print(int(time.time()*1000))" 2>/dev/null \
|
||||||
|
|| expr "$(date +%s)" \* 1000 ;;
|
||||||
|
*) printf '%s\n' "$_t" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
_enforce_rate_limit() {
|
||||||
|
[ "$RATE_LIMIT_MS" -le 0 ] 2>/dev/null && return 0
|
||||||
|
if [ -f "$_RATE_STATE_FILE" ]; then
|
||||||
|
_last=$(cat "$_RATE_STATE_FILE" 2>/dev/null || printf '0')
|
||||||
|
_now=$(_now_ms)
|
||||||
|
_elapsed=$(( _now - _last ))
|
||||||
|
if [ "$_elapsed" -lt "$RATE_LIMIT_MS" ]; then
|
||||||
|
_remaining=$(( RATE_LIMIT_MS - _elapsed ))
|
||||||
|
printf 'whisper: rate limited — retry in %s ms\n' "$_remaining" >&2
|
||||||
|
exit 5
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
mkdir -p "$(dirname "$_RATE_STATE_FILE")"
|
||||||
|
_now_ms > "$_RATE_STATE_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
# --- arg parsing ---
|
# --- arg parsing ---
|
||||||
TRANSPORT="telegram"
|
TRANSPORT="telegram"
|
||||||
MESSAGE=""
|
MESSAGE=""
|
||||||
|
TEMPLATE=""
|
||||||
|
PARAM_COUNT=0
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-m|--message)
|
-m|--message)
|
||||||
[ -n "${2+x}" ] || die "missing value for $1" 1
|
[ -n "${2+x}" ] || die "missing value for $1" 1
|
||||||
MESSAGE="$2"; shift 2 ;;
|
MESSAGE="$2"; shift 2 ;;
|
||||||
-t|--transport)
|
-t|--template)
|
||||||
|
[ -n "${2+x}" ] || die "missing value for $1" 1
|
||||||
|
TEMPLATE="$2"; shift 2 ;;
|
||||||
|
--via|--transport|-T)
|
||||||
[ -n "${2+x}" ] || die "missing value for $1" 1
|
[ -n "${2+x}" ] || die "missing value for $1" 1
|
||||||
TRANSPORT="$2"; shift 2 ;;
|
TRANSPORT="$2"; shift 2 ;;
|
||||||
|
-p)
|
||||||
|
shift
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-*) break ;;
|
||||||
|
*)
|
||||||
|
PARAM_COUNT=$((PARAM_COUNT + 1))
|
||||||
|
eval "_P${PARAM_COUNT}=\$1"
|
||||||
|
shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
usage ;;
|
usage ;;
|
||||||
*)
|
*)
|
||||||
@@ -45,12 +166,20 @@ while [ $# -gt 0 ]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
[ -n "$MESSAGE" ] || die "message is required (-m)" 1
|
if [ -n "$MESSAGE" ] && [ -n "$TEMPLATE" ]; then
|
||||||
|
die "cannot use both --message and --template" 1
|
||||||
|
fi
|
||||||
|
if [ -z "$MESSAGE" ] && [ -z "$TEMPLATE" ]; then
|
||||||
|
die "either --message or --template is required" 1
|
||||||
|
fi
|
||||||
|
if [ "$PARAM_COUNT" -gt 0 ] && [ -z "$TEMPLATE" ]; then
|
||||||
|
die "-p can only be used with --template" 1
|
||||||
|
fi
|
||||||
|
|
||||||
# --- env validation ---
|
# --- env validation ---
|
||||||
validate_telegram_env() {
|
validate_telegram_env() {
|
||||||
[ -n "$TELEGRAM_BOT_TOKEN" ] || die "TELEGRAM_BOT_TOKEN is not set" 1
|
[ -n "$TELEGRAM_BOT_TOKEN" ] || die "TELEGRAM_BOT_TOKEN is not set" 3
|
||||||
[ -n "$TELEGRAM_CHAT_ID" ] || die "TELEGRAM_CHAT_ID is not set" 1
|
[ -n "$TELEGRAM_CHAT_ID" ] || die "TELEGRAM_CHAT_ID is not set" 3
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- transports ---
|
# --- transports ---
|
||||||
@@ -62,7 +191,7 @@ send_telegram() {
|
|||||||
response=$(curl -s -X POST "$url" \
|
response=$(curl -s -X POST "$url" \
|
||||||
-d chat_id="$TELEGRAM_CHAT_ID" \
|
-d chat_id="$TELEGRAM_CHAT_ID" \
|
||||||
--data-urlencode text="$MESSAGE" \
|
--data-urlencode text="$MESSAGE" \
|
||||||
-d parse_mode="HTML")
|
-d parse_mode="HTML") || die "curl request failed" 4
|
||||||
|
|
||||||
if echo "$response" | grep -q '"ok":true'; then
|
if echo "$response" | grep -q '"ok":true'; then
|
||||||
printf 'whisper: message sent\n' >&2
|
printf 'whisper: message sent\n' >&2
|
||||||
@@ -71,11 +200,17 @@ send_telegram() {
|
|||||||
local err
|
local err
|
||||||
err=$(echo "$response" | sed -n 's/.*"description":"\([^"]*\)".*/\1/p')
|
err=$(echo "$response" | sed -n 's/.*"description":"\([^"]*\)".*/\1/p')
|
||||||
printf 'whisper: send failed: %s\n' "${err:-unknown error}" >&2
|
printf 'whisper: send failed: %s\n' "${err:-unknown error}" >&2
|
||||||
exit 2
|
exit 4
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- resolve template if used ---
|
||||||
|
if [ -n "$TEMPLATE" ]; then
|
||||||
|
resolve_template
|
||||||
|
fi
|
||||||
|
|
||||||
# --- dispatch ---
|
# --- dispatch ---
|
||||||
|
_enforce_rate_limit
|
||||||
case "$TRANSPORT" in
|
case "$TRANSPORT" in
|
||||||
telegram) send_telegram ;;
|
telegram) send_telegram ;;
|
||||||
*) die "unknown transport: $TRANSPORT" 1 ;;
|
*) die "unknown transport: $TRANSPORT" 1 ;;
|
||||||
|
|||||||
Reference in New Issue
Block a user