penguin/utils

my env utils

bash/dist/rbashrc

raw ยท 13166 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="61835cc27d7685e8c77479d6e129da27876e67750ae33b84bbd304ae94b3d327"


# ---- 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 force_redownload=0
	local sig url name key file

	OPTIND=1
	while getopts ":r" opt; do
		case "$opt" in
			r)
				force_redownload=1
				;;
			\?)
				echo "usage: rbash_cache [-r] SIG URL [NAME]" >&2
				return 2
				;;
		esac
	done
	shift $((OPTIND - 1))

	if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then
		echo "usage: rbash_cache [-r] SIG URL [NAME]" >&2
		return 2
	fi

	sig="$1"
	url="$2"
	name="${3:-}"

	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 [ -n "$name" ]; then
		file="$RBASH_CACHE_DIR/$name"
	else
		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"
	fi

	if [ "$force_redownload" -eq 1 ]; then
		rm -f "$file"
	fi

	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() {

	if [ "$1" = "-f" ]; then
		sources_list=$(rbash_cache -r "$RBASH_SOURCES_SUM" "$(rbash_sources_list)" "sources.list")
	fi

	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