bash/dist/rbashrc
raw ยท 12543 bytes
#!/usr/bin/env bash
case $- in
*i*) ;;
*) return ;;
esac
__RBASH_SCRIPT="${BASH_SOURCE[0]}"
RBASH_HOME="${RBASH_HOME:-$HOME/.rbash}"
RBASH_REMOTE="${RBASH_REMOTE:-https://sgit.k8s.astropenguin.net}"
RBASH_REPO="${RBASH_REPO:-penguin/utils}"
RBASH_REF="${RBASH_REF:-master}"
RBASH_PATH="${RBASH_PATH:-bash}"
RBASH_CACHE_DIR="$RBASH_HOME/cache"
RBASH_CONFIG="$RBASH_HOME/config"
RBASH_ENV="$RBASH_HOME/env"
RBASH_MERGED="$RBASH_CACHE_DIR/merged"
RBASH_BIN="$RBASH_HOME/bin"
RBASH_SOURCES_SUM="e221c016049e096ecef46d03b52864fe99ec84f6dba560707ca4f1a44368e6a9"
# ---- rbash: bash/core/00_env.bash ----
# Host prompt placeholders can be replaced by rbash-upgrade/install.
RHOSTNAME="${RHOSTNAME:-<HOSTNAME>}"
RDOMAIN="${RDOMAIN:-<DOMAIN>}"
RCOLOR="${RCOLOR:-<RCOLOR>}"
if [ "$RHOSTNAME" = "<HOSTNAME>" ]; then
RHOSTNAME="$(whoami)"
RDOMAIN="@HOST_NAME"
RCOLOR=$(( RANDOM * 6 / 32767 + 1 ))
fi
RBASH_SIGNING_FPR="7C1E948D841254C569EDFBDED5CF5FB1CFAD27C3"
_RBASH_LOADED=
# ---- rbash: bash/core/05_defaults.bash ----
__rbash_init_config() {
if [ -f "$RBASH_CONFIG" ]; then
return 0
fi
cat > "$RBASH_CONFIG" <<'EOF'
rbash_load "sources/10_aliases"
rbash_load "sources/12_shortcuts"
rbash_load "sources/40_go-command"
rbash_load "sources/41_pivot-command"
EOF
chmod 600 "$RBASH_CONFIG"
}
# ---- rbash: bash/core/10_shell.bash ----
# Source global definitions.
if [ -f /etc/bashrc ]; then
# shellcheck source=/etc/bashrc
source /etc/bashrc
fi
HISTCONTROL=ignoreboth
HISTSIZE=1000
HISTFILESIZE=2000
shopt -s histappend
shopt -s checkwinsize
if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
debian_chroot="$(cat /etc/debian_chroot)"
fi
if [ -f "$HOME/.bash_aliases" ]; then
# shellcheck source=/dev/null
source "$HOME/.bash_aliases"
fi
if ! shopt -oq posix; then
if [ -f /usr/share/bash-completion/bash_completion ]; then
# shellcheck source=/usr/share/bash-completion/bash_completion
source /usr/share/bash-completion/bash_completion
elif [ -f /etc/bash_completion ]; then
# shellcheck source=/etc/bash_completion
source /etc/bash_completion
fi
fi
__func_head() {
echo "Usage: ${FUNCNAME[1]} $*"
}
__func_help() {
echo " ${FUNCNAME[1]} $*"
}
# ---- rbash: bash/core/20_io.bash ----
rbash_url() {
local path="$1"
printf '%s/%s/raw/%s/%s/%s\n' \
"$RBASH_REMOTE" "$RBASH_REPO" "$RBASH_REF" "$RBASH_PATH" "$path"
}
rbash_download() {
local url="$1"
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$url" || {
local rc=$?
echo "rbash: download failed: $url" >&2
echo "rbash: curl exit code: $rc" >&2
return "$rc"
}
elif command -v wget >/dev/null 2>&1; then
wget -qO- "$url" || {
local rc=$?
echo "rbash: download failed: $url" >&2
echo "rbash: wget exit code: $rc" >&2
return "$rc"
}
else
echo "rbash: curl or wget is required" >&2
return 127
fi
}
function rbash_download_sig {
local _URL _LOC _SIG tmp
_SIG="$1"
_URL="$2"
_LOC="$3"
tmp=$( mktemp )
rbash_download "$_URL" > "$tmp"
sha256sum "$tmp" | grep -q "$_SIG"
if [ $? -eq 0 ]; then
mv "$tmp" "$_LOC"
else
echo "Download $_LOC: Signature mismatch" >&2
return 1
fi
}
rbash_cache() {
local sig="$1"
local url="$2"
local key file
if [ -z "${RBASH_CACHE_DIR:-}" ]; then
echo "rbash: RBASH_CACHE_DIR is not set" >&2
return 1
fi
mkdir -p "$RBASH_CACHE_DIR" || {
echo "rbash: failed to create cache dir: $RBASH_CACHE_DIR" >&2
return 1
}
if command -v md5sum >/dev/null 2>&1; then
key="$(printf '%s' "$url" | md5sum | cut -d' ' -f1)"
else
key="$(printf '%s' "$url" | md5 | cut -d' ' -f1)"
fi
file="$RBASH_CACHE_DIR/$key"
if [ ! -f "$file" ]; then
rbash_download_sig "$sig" "$url" "$file" || {
local rc=$?
rm -f "$file"
echo "rbash: download failed: $url" >&2
return "$rc"
}
chmod 700 "$file"
fi
printf '%s\n' "$file"
}
_require_jq() {
if ! command -v jq >/dev/null 2>&1; then
echo "This command requires jq." >&2
return 1
fi
}
# ---- rbash: bash/core/30_loader.bash ----
rbash-reload() {
_RBASH_LOADED=
_RBASH_MERGE_START=1
if [ "$1" = "-f" ]; then
rm -rf "$RBASH_CACHE_DIR"
fi
mkdir -p "$RBASH_CACHE_DIR"
rm -f "$RBASH_MERGED"
: > "$RBASH_MERGED" || {
echo "rbash: failed to write merged cache: $RBASH_MERGED" >&2
_RBASH_MERGE_START=
return 1
}
# shellcheck source=/dev/null
source "$RBASH_CONFIG"
_RBASH_MERGE_START=
_RBASH_LOADED=1
}
__rbash_autoload() {
if [ -n "${_RBASH_LOADED:-}" ]; then
return 0
fi
if [ -f "$RBASH_MERGED" ]; then
# shellcheck source=/dev/null
source "$RBASH_MERGED" && {
_RBASH_LOADED=1
return 0
}
echo "rbash: merged cache failed, rebuilding" >&2
rm -f "$RBASH_MERGED"
fi
rbash-reload
}
rbash_load() {
local rel="$1"
local url file
# Normal startup should not individually fetch sources.
# It should source RBASH_MERGED via __rbash_autoload.
if [ -z "${_RBASH_MERGE_START:-}" ]; then
__rbash_autoload
return 0
fi
echo "Load: $rel"
url="$(rbash_url "$rel")"
key="$(printf '%s' "$rel" | sed "s|^sources/||g" | grep -o '^[0-9]\+' || true)"
sig="${_RBASH_SOURCES_SUM["s$key"]}"
file="$(rbash_cache "$sig" "$url")" || {
echo " ..download failed" >&2
return 1
}
# shellcheck source=/dev/null
if source "$file"; then
{
printf 'echo "Load: %s"\n' "$rel"
cat "$file"
printf '\n'
} >> "$RBASH_MERGED"
else
echo " ..failed" >&2
printf 'echo "Skipped: %s. Use rbash-reload to try again."\n' "$rel" >> "$RBASH_MERGED"
fi
chmod 700 "$RBASH_MERGED"
}
rbash-run() {
local rel="$1"
local file
shift || true
echo "Getting: $rel"
file="$(rbash_cache "$(rbash_url "$rel")")" || return 1
"$file" "$@"
}
# ---- rbash: bash/core/40_sources.bash ----
rbash_sources_list() {
printf '%s/%s/raw/master/bash/dist/sources.list\n' \
"$RBASH_REMOTE" "$RBASH_REPO"
}
declare -gA _RBASH_DICT
declare -gA _RBASH_SOURCES_SUM
rbash_sources_load() {
local hash path item key sources_list
_RBASH_DICT=()
_RBASH_SOURCES_SUM=()
sources_list=$(rbash_cache "$RBASH_SOURCES_SUM" "$(rbash_sources_list)" "sources.list")
while read -r hash path; do
[ -n "$hash" ] || continue
[ -n "$path" ] || continue
item="${path#sources/}"
key="$(printf '%s' "$item" | grep -o '^[0-9]\+' || true)"
[ -n "$key" ] || continue
_RBASH_DICT["s$key"]="$item"
_RBASH_SOURCES_SUM["s$key"]="$hash"
done < "$sources_list"
}
rbash-list() {
local key item enabled_marker
rbash_sources_load || return 1
echo "Available sources:"
while IFS= read -r key; do
item="${_RBASH_DICT[$key]}"
enabled_marker=" "
if grep -q -F "rbash_load \"sources/$item\"" "$RBASH_CONFIG"; then
enabled_marker="*"
fi
printf ' %s %s\n' "$enabled_marker" "$item"
done < <(printf '%s\n' "${!_RBASH_DICT[@]}" | sort -V)
}
r2ensource() {
rbash-list || return 1
local ids id selected tmp
local -a enable=()
read -r -p "Enter the script id XX, delimited by space: " ids
for id in $ids; do
selected="${_RBASH_DICT["s$id"]}"
if [ -z "$selected" ]; then
echo "No such id: $id" >&2
return 1
fi
if grep -q -F "rbash_load \"sources/$selected\"" "$RBASH_CONFIG"; then
echo "$selected is already enabled."
continue
fi
enable+=("$selected")
done
tmp="$(mktemp)"
cp "$RBASH_CONFIG" "$tmp"
for selected in "${enable[@]}"; do
echo "rbash_load \"sources/$selected\"" >> "$tmp"
echo "Enabling $selected"
done
sort -u "$tmp" > "$RBASH_CONFIG"
rm -f "$tmp"
rm -rf "$RBASH_CACHE_DIR"
}
r2dissource() {
local ids id tmp pattern
echo "Currently enabled sources:"
grep -E '^rbash_load "sources/' "$RBASH_CONFIG" |
sed 's/^rbash_load "sources\///; s/"$//' |
sed 's/^/ /'
read -r -p "Enter the script id XX, delimited by space: " ids
tmp="$(mktemp)"
cp "$RBASH_CONFIG" "$tmp"
for id in $ids; do
pattern="rbash_load \"sources/${id}_"
if ! grep -q -F "$pattern" "$tmp"; then
echo "No such id: $id" >&2
rm -f "$tmp"
return 1
fi
echo "Disabling $id"
grep -v -F "$pattern" "$tmp" > "$tmp.next"
mv "$tmp.next" "$tmp"
done
sort -u "$tmp" > "$RBASH_CONFIG"
rm -f "$tmp"
rm -rf "$RBASH_CACHE_DIR"
}
# ---- rbash: bash/core/50_upgrade.bash ----
rbash_verify_upgrade() {
local new_rbashrc="$1"
local sig_file="$2"
local key_file="$3"
local gnupg_home key_fpr rc
command -v gpg >/dev/null 2>&1 || {
echo "gpg is required for rbash upgrade verification"
return 1
}
[ -n "${RBASH_SIGNING_FPR:-}" ] || {
echo "RBASH_SIGNING_FPR is not set"
echo "Refusing to upgrade without a pinned signing key fingerprint"
return 1
}
[ -f "$new_rbashrc" ] || {
echo "missing rbashrc: $new_rbashrc"
return 1
}
[ -f "$sig_file" ] || {
echo "missing signature: $sig_file"
return 1
}
[ -f "$key_file" ] || {
echo "missing public key: $key_file"
return 1
}
gnupg_home="$(mktemp -d)"
chmod 700 "$gnupg_home"
GNUPGHOME="$gnupg_home" gpg --quiet --batch --import "$key_file" >/dev/null 2>&1 || {
rc=$?
rm -rf "$gnupg_home"
echo "failed to import rbash signing key"
return "$rc"
}
key_fpr="$(
GNUPGHOME="$gnupg_home" gpg --with-colons --fingerprint 2>/dev/null |
awk -F: '$1 == "fpr" { print $10; exit }'
)"
if [ "$key_fpr" != "$RBASH_SIGNING_FPR" ]; then
rm -rf "$gnupg_home"
echo "rbash signing key fingerprint mismatch"
echo "expected: $RBASH_SIGNING_FPR"
echo "got: ${key_fpr:-<none>}"
return 1
fi
GNUPGHOME="$gnupg_home" gpg \
--quiet \
--batch \
--verify "$sig_file" "$new_rbashrc" >/dev/null 2>&1
rc=$?
rm -rf "$gnupg_home"
if [ "$rc" -ne 0 ]; then
echo "rbash signature verification failed"
return "$rc"
fi
return 0
}
rbash-upgrade() {
local target backup backup_suffix
local tmp_dir tmp_rbashrc tmp_sig tmp_key
local base_url rbashrc_url sig_url key_url
local rc
target="${__RBASH_SCRIPT:-$HOME/.bashrc}"
base_url="$(rbash_url "dist")"
rbashrc_url="$base_url/rbashrc"
sig_url="$base_url/rbashrc.asc"
key_url="$base_url/rbash-signing-key.asc"
command -v gpg >/dev/null 2>&1 || {
echo "gpg is required"
return 1
}
tmp_dir="$(mktemp -d)"
tmp_rbashrc="$tmp_dir/rbashrc"
tmp_sig="$tmp_dir/rbashrc.asc"
tmp_key="$tmp_dir/rbash-signing-key.asc"
__rbash_upgrade_cleanup() {
rm -rf "$tmp_dir"
}
trap __rbash_upgrade_cleanup RETURN
echo "Downloading rbashrc..."
rbash_download "$rbashrc_url" > "$tmp_rbashrc" || return 1
echo "Downloading rbashrc signature..."
rbash_download "$sig_url" > "$tmp_sig" || return 1
echo "Downloading rbash signing public key..."
rbash_download "$key_url" > "$tmp_key" || return 1
echo "Verifying rbashrc signature..."
rbash_verify_upgrade "$tmp_rbashrc" "$tmp_sig" "$tmp_key" || {
echo "rbash upgrade aborted"
return 1
}
backup_suffix="$(date +%Y%m%d%H%M%S)"
backup="$target.bak.$backup_suffix"
echo "Updating $target"
echo "Backup: $backup"
if [ -f "$target" ]; then
cp "$target" "$backup" || {
echo "failed to create backup: $backup"
return 1
}
fi
cp "$tmp_rbashrc" "$target" || {
rc=$?
echo "failed to install new rbashrc"
if [ -f "$backup" ]; then
echo "restoring backup"
cp "$backup" "$target" || true
fi
return "$rc"
}
chmod 600 "$target" 2>/dev/null || true
echo "rbash upgrade complete"
}
# ---- rbash: bash/core/80_public_api.bash ----
rbash-host() {
local host="${1:-}"
local domain="${2:-}"
local color="${3:-}"
if [ -z "$host" ] || [ -z "$domain" ]; then
echo "Usage: rbash-host HOST DOMAIN [COLOR]" >&2
echo "Example: rbash-host penguin @macstudio 6" >&2
return 1
fi
mkdir -p "$RBASH_HOME"
{
printf 'RHOSTNAME=%q\n' "$host"
printf 'RDOMAIN=%q\n' "$domain"
if [ -n "$color" ]; then
printf 'RCOLOR=%q\n' "$color"
fi
} > "$RBASH_ENV"
echo "Wrote $RBASH_ENV"
# shellcheck source=/dev/null
source "$RBASH_ENV"
__rbash_setup_prompt
}
# ---- rbash: bash/core/90_prompt.bash ----
__rbash_setup_prompt() {
local shelld="\$"
case "$OSTYPE" in
cygwin*)
if net session >/dev/null 2>&1; then
shelld="#"
fi
;;
esac
export PS1="This is ${RHOSTNAME}\e[1;3${RCOLOR}m${RDOMAIN}\e[0m: \w\n$shelld "
export EDITOR="${EDITOR:-vim}"
export PATH="$RBASH_BIN:$PATH"
}
# ---- rbash: bash/core/99_main.bash ----
__rbash_main() {
mkdir -p "$RBASH_HOME" "$RBASH_CACHE_DIR" "$RBASH_BIN"
__rbash_init_config
if [ -f "$RBASH_ENV" ]; then
# shellcheck source=/dev/null
source "$RBASH_ENV"
fi
rbash_sources_load
__rbash_autoload
__rbash_setup_prompt
}
__rbash_main
unset -f __rbash_main