#!/bin/sh
# reportbin installer for macOS and Linux. Needs only sh and curl.
#
# curl -fsSL https://reportbin.dev/install.sh | sh
#
# Installs the `reportbin` command to ~/.local/bin and saves the server URL and
# upload token to ~/.config/reportbin/env. To uninstall, delete both.
#
# Optional environment:
# REPORTBIN_TOKEN upload token, skips the prompt
# REPORTBIN_URL server to use instead of https://reportbin.dev
# REPORTBIN_BIN_DIR where to install the command, default ~/.local/bin
set -eu
main () {
url=${REPORTBIN_URL:-https://reportbin.dev}
url=${url%/}
bin_dir=${REPORTBIN_BIN_DIR:-$HOME/.local/bin}
config=$HOME/.config/reportbin/env
command -v curl >/dev/null 2>&1 || die 'curl is required (Ubuntu: sudo apt install -y curl)'
token=${REPORTBIN_TOKEN:-}
if [ -n "$token" ]; then
token_ok "$token" || die 'the server rejected this token'
elif token=$(saved_token) && [ -n "$token" ] && token_ok "$token"; then
say "Using the saved token from $config"
else
[ -z "$token" ] || say 'The saved token was rejected by the server.'
token=$(ask_token)
[ -n "$token" ] || die 'no token given'
token_ok "$token" || die 'the server rejected this token'
fi
mkdir -p "$bin_dir" "${config%/*}"
# Written fresh and moved into place, so the token file is always private
# and an interrupted install never leaves a half-written file behind.
(umask 077 && printf 'REPORTBIN_URL=%s\nREPORTBIN_TOKEN=%s\n' "$url" "$token" >"$config.tmp")
mv -f "$config.tmp" "$config"
write_cli >"$bin_dir/.reportbin.tmp"
chmod 755 "$bin_dir/.reportbin.tmp"
mv -f "$bin_dir/.reportbin.tmp" "$bin_dir/reportbin"
say "Installed $bin_dir/reportbin"
case ":$PATH:" in
*":$bin_dir:"*) ;;
*) add_to_path ;;
esac
say ''
say 'Try it:'
say " echo '
Hello
' | reportbin -"
}
say () { printf '%s\n' "$*"; }
die () { printf 'reportbin install: %s\n' "$*" >&2; exit 1; }
# A saved token is only reused for the server it was saved for.
saved_token () {
[ -f "$config" ] && [ "$(sed -n 's/^REPORTBIN_URL=//p' "$config")" = "$url" ] || return 0
sed -n 's/^REPORTBIN_TOKEN=//p' "$config"
}
ask_token () {
(: /dev/null || die 'no terminal to ask for the token; run with REPORTBIN_TOKEN='
printf 'Upload token for %s: ' "$url" >/dev/tty
# Input is hidden while typing; make sure Ctrl+C does not leave the terminal that way.
trap 'stty echo /dev/null; printf "\n" >/dev/tty; exit 130' INT TERM HUP
stty -echo /dev/null || true
IFS= read -r answer /dev/null || true
trap - INT TERM HUP
printf '\n' >/dev/tty
printf '%s' "$answer" | tr -d '[:space:]'
}
# Checks a token without storing anything: an empty upload is rejected with 400
# after auth succeeds, and with 401 when the token is wrong. 429 means the token
# is valid but its uploads are used up for the hour.
token_ok () {
escaped=$(printf '%s' "$1" | sed 's/[\\"]/\\&/g')
status=$(curl -sS -o /dev/null -w '%{http_code}' --connect-timeout 10 --config /dev/fd/3 \
-H 'Content-Type: text/html' --data-binary '' "$url/api/reports" 3<>"$rc"
say "Added $bin_dir to PATH in $rc. Open a new terminal to use it."
}
write_cli () {
cat <<'CLI'
#!/bin/sh
# reportbin: upload an HTML report and print its link.
set -eu
usage () {
cat <
| reportbin -
reportbin --hi ""
Uploads an HTML report and prints its link, or sends feedback to the server.
Reads REPORTBIN_URL and REPORTBIN_TOKEN from the environment
or from ~/.config/reportbin/env (override with REPORTBIN_CONFIG).
EOF
}
die () { printf 'reportbin: %s\n' "$*" >&2; exit 1; }
case ${1:-} in
-h|--help) usage; exit 0 ;;
# --feedback is an unlisted alias, for people who guess it.
--hi|--feedback) shift; action=feedback; [ -n "$*" ] || { usage >&2; exit 2; } ;;
*) action=upload; [ $# -eq 1 ] || { usage >&2; exit 2; } ;;
esac
# Environment variables win over the config file.
url=${REPORTBIN_URL:-}
token=${REPORTBIN_TOKEN:-}
config=${REPORTBIN_CONFIG:-$HOME/.config/reportbin/env}
if [ -f "$config" ]; then
url=${url:-$(sed -n 's/^REPORTBIN_URL=//p' "$config")}
token=${token:-$(sed -n 's/^REPORTBIN_TOKEN=//p' "$config")}
fi
[ -n "$url" ] && [ -n "$token" ] || die "REPORTBIN_URL and REPORTBIN_TOKEN must be set (in the environment or $config)"
url=${url%/}
# The token goes through a config descriptor so it never shows up in `ps`.
escaped=$(printf '%s' "$token" | sed 's/[\\"]/\\&/g')
# Usage: send
send () {
curl -sS -w '%{http_code}' --connect-timeout 10 --config /dev/fd/3 \
-H "Content-Type: $2" --data-binary "$3" "$url/api/$1" 3<