From ee8f5f528a945a37eb7713d7d62febb76dea0438 Mon Sep 17 00:00:00 2001 From: Darren 'Tadgy' Austin Date: Sun, 25 Aug 2024 20:36:06 +0100 Subject: [PATCH] Add updated bash startup files. --- .bash_logout | 3 +- .bash_profile | 78 ++---- .bash_profile.d/gitconfig | 21 ++ .bash_profile.d/perl5 | 8 + .bash_profile.d/screen | 48 ++++ .bashrc | 556 ++------------------------------------ .bashrc.d/git | 220 +++++++++++++++ .bashrc.d/imagebin | 11 + .bashrc.d/nanorc | 25 ++ .bashrc.d/ssh | 243 +++++++++++++++++ .gitattributesdb | 13 +- 11 files changed, 633 insertions(+), 593 deletions(-) create mode 100644 .bash_profile.d/gitconfig create mode 100755 .bash_profile.d/perl5 create mode 100755 .bash_profile.d/screen create mode 100755 .bashrc.d/git create mode 100755 .bashrc.d/imagebin create mode 100755 .bashrc.d/nanorc create mode 100755 .bashrc.d/ssh diff --git a/.bash_logout b/.bash_logout index f59c34b..8b6422e 100644 --- a/.bash_logout +++ b/.bash_logout @@ -20,5 +20,6 @@ hash ssh-add ssh-agent >/dev/null 2>&1 && { } # Update the ~/.ssh/agents file. - __read_ssh_agents && __write_ssh_agents + __write_ssh_agents + (( $? == 2 )) && __read_ssh_agents && __write_ssh_agents } diff --git a/.bash_profile b/.bash_profile index 8ec9ecb..626f2db 100644 --- a/.bash_profile +++ b/.bash_profile @@ -1,17 +1,14 @@ #!/bin/bash - not strictly necessary, but helps nano with syntax highlighting. +# Bash shell environmental set up. -# Source bash specific set up, -[[ -f "$HOME/.bashrc" ]] && . "$HOME/.bashrc" - -# Environment. -export EDITOR="nano" +# Common environment. export LANG="en_GB.UTF-8" export LC_COLLATE="POSIX" # 'C' causes issues with some applications # export LC_CTYPE="POSIX" # Not sure why I set this in the first place... export LESS='-RM -j.5 -i -PM?f%F:stdin. -- Page %dt of %D -- %lt/%L (%Pt\%)$' -export PAGER="less" -export VISUAL="$EDITOR" +hash less >/dev/null 2>&1 && export PAGER="less" hash lesspipe >/dev/null 2>&1 && eval "$(SHELL=/bin/sh lesspipe)" +hash nano >/dev/null 2>&1 && export EDITOR="nano" && export VISUAL="$EDITOR" # Platform specific set up. PLATFORM="${PLATFORM:-$(uname -s)}" @@ -21,9 +18,10 @@ if [[ "$PLATFORM" = "Linux" ]]; then export I_WANT_A_BROKEN_PS=1 export LYNX_CFG="$HOME/.lynx.cfg" export LYNX_LSS="$HOME/.lynx.lss" - export MANPAGER="less" + export MANPAGER="$PAGER" export MANPATH="$HOME/.local/share/man:$MANPATH" export PATH="/opt/sbin:/opt/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + hash dircolors >/dev/null 2>&1 && eval "$(dircolors -b "$HOME/.dir_colors")" hash gpgconf >/dev/null 2>&1 && { GPG_SOCK_DIR="$(gpgconf --list-dirs | awk -F : '/socketdir:/ { print $2 }')" @@ -33,6 +31,7 @@ if [[ "$PLATFORM" = "Linux" ]]; then unset GPG_SOCK_DIR } hash gpg-connect-agent >/dev/null 2>&1 && gpg-connect-agent updatestartuptty /bye >/dev/null 2>&1 + [[ ! -e "$HOME/.config/lxterminal/lxterminal.conf" ]] && ( cd "$HOME/.config/lxterminal" 2>/dev/null || exit 1 [[ -e "lxterminal.conf-${HOSTNAME%%.*}" ]] && ln -sf "lxterminal.conf-${HOSTNAME%%.*}" "lxterminal.conf" @@ -46,57 +45,20 @@ else printf "%s: %s\\n" "${BASH_SOURCE##*/}" "unsupported platform: $PLATFORM" >&2 fi -# Add bin directories to PATH. +# Add local perl directories to PATH if they exist. +[[ -d "$HOME/.local/perl5/bin" ]] && export PATH="$HOME/.local/perl5/bin:$PATH" +[[ -d "$HOME/.local/perl5/sbin" ]] && export PATH="$HOME/.local/perl5/sbin:$PATH" + +# Add extra bin directories to PATH if they exist. [[ -d "$HOME/files/bin" ]] && export PATH="$HOME/files/bin:$PATH" [[ -d "$HOME/.local/bin" ]] && export PATH="$HOME/.local/bin:$PATH" [[ -d "$HOME/bin" ]] && export PATH="$HOME/bin:$PATH" -# Screen. -hash screen >/dev/null 2>&1 && { - [[ -e "$HOME/.screenrc-${HOSTNAME%%.*}" ]] && export SCREENRC="$HOME/.screenrc-${HOSTNAME%%.*}" - export SCREENDIR="$HOME/.screen-${HOSTNAME%%.*}" - if [[ -n "$SSH_TTY" ]]; then - if [[ -n "$STY" ]]; then - # shellcheck disable=SC2154 - printf "%s\\n\\n" "Screen $STY, window $WINDOW." - else - SCREENS="$(screen -ls | grep '[[:alpha:]]' | grep -E -v '^([[:digit:]]+|No) Socket(s)?')" - if [[ -n "$SCREENS" ]]; then - printf "%s\\n\\n" "$SCREENS" - else - printf "%s\\n\\n" "No screens." - fi - unset SCREENS - fi - elif [[ -n "$STY" ]]; then - # shellcheck disable=SC2154 - printf "%s\\n\\n" "Screen $STY, window $WINDOW." - else - TTY="$(tty | cut -d/ -f3-)" - SCREENS="$(screen -ls | grep -F "${HOSTNAME%%.*}" | grep -F "${TTY//\//-}")" - # This has to be an echo, not printf. - case "$(echo "${SCREENS:--n}" | wc -l)" in - 0) - # No screens found - start a new instance if on a tty. - # Disabled this because screens should be started on chuckie not the desktop/laptop. - # [[ "$TTY" == tty* ]] && screen - : - ;; - 1) - # Just one screen - reconnect if it's not dead. - if (( $(printf "%s" "$SCREENS" | grep -c -F 'Dead') == 1 )); then - printf "%s:\\n" "Found dead screen for $TTY" - printf "%s\\n" "$SCREENS" | sed -e 's/^/ /g' - else - screen -dr "${TTY//\//-}.${HOSTNAME%%.*}" - fi - ;; - *) - # Multiple screens - output a list - printf "%s:\\n" "Multiple screens found for $TTY" - printf "%s\\n" "$SCREENS" | sed -e 's/^/ /g' - ;; - esac - fi - unset SCREENS TTY -} +# Read in the environment from .bash_profile.d/*. +for FILE in "$HOME"/.bash_profile.d/*; do + [[ -x "$FILE" ]] && source "$FILE" +done +unset FILE + +# Source bash specific set up, +[[ -f "$HOME/.bashrc" ]] && . "$HOME/.bashrc" diff --git a/.bash_profile.d/gitconfig b/.bash_profile.d/gitconfig new file mode 100644 index 0000000..8c8ce4f --- /dev/null +++ b/.bash_profile.d/gitconfig @@ -0,0 +1,21 @@ +#!/bin/bash - not strictly necessary, but helps nano with syntax highlighting. + +# Symlink the .gitconfig file to the most specific .gitconfig-* file. +if [[ -e "$HOME/.gitconfig-$USER@$HOSTNAME" ]]; then + FILE=".gitconfig-$USER@$HOSTNAME" +elif [[ -e "$HOME/.gitconfig-$USER@*.${HOSTNAME#*.}" ]]; then + FILE=".gitconfig-$USER@*.${HOSTNAME#*.}" +elif [[ -e "$HOME/.gitconfig-$USER@*" ]]; then + FILE=".gitconfig-$USER@*" +elif [[ -e "$HOME/.gitconfig-*@$HOSTNAME" ]]; then + FILE=".gitconfig-*@$HOSTNAME" +elif [[ -e "$HOME/.gitconfig-*.${HOSTNAME#*.}" ]]; then + FILE=".gitconfig-*.${HOSTNAME#*.}" +elif [[ -e "$HOME/.gitconfig-default" ]]; then + FILE=".gitconfig-default" +else + (cd "$HOME" && [[ -L ".gitconfig" ]] && rm -f ".gitconfig") + printf "%s: %s\\n" "${BASH_SOURCE##*/}" "failed to update .gitconfig symlink" >&2 +fi +[[ -n "$FILE" ]] && (cd "$HOME" && ln -sf "$FILE" ".gitconfig") +unset FILE diff --git a/.bash_profile.d/perl5 b/.bash_profile.d/perl5 new file mode 100755 index 0000000..0369b1a --- /dev/null +++ b/.bash_profile.d/perl5 @@ -0,0 +1,8 @@ +#!/bin/bash - not strictly necessary, but helps nano with syntax highlighting. + +hash perl >/dev/null 2>&1 && { + export PERL_MB_OPT="--install_base \"$HOME/.local/perl5\"" + export PERL_MM_OPT="INSTALL_BASE=$HOME/.local/perl5" + export PERL_LOCAL_LIB_ROOT="$HOME/.local/perl5${PERL_LOCAL_LIB_ROOT:+:$PERL_LOCAL_LIB_ROOT}" + export PERL5LIB="$HOME/.local/perl5/lib/perl5${PERL5LIB:+:$PERL5LIB}" +} diff --git a/.bash_profile.d/screen b/.bash_profile.d/screen new file mode 100755 index 0000000..10fe1b2 --- /dev/null +++ b/.bash_profile.d/screen @@ -0,0 +1,48 @@ +#!/bin/bash - not strictly necessary, but helps nano with syntax highlighting. + +hash screen >/dev/null 2>&1 && { + [[ -e "$HOME/.screenrc-${HOSTNAME%%.*}" ]] && export SCREENRC="$HOME/.screenrc-${HOSTNAME%%.*}" + export SCREENDIR="$HOME/.screen-${HOSTNAME%%.*}" + if [[ -n "$SSH_TTY" ]]; then + if [[ -n "$STY" ]]; then + printf "%s\\n\\n" "Screen $STY, window $WINDOW." + else + SCREENS="$(screen -ls | command grep '[[:alpha:]]' | command grep -E -v '^([[:digit:]]+|No) Socket(s)?')" + if [[ -n "$SCREENS" ]]; then + printf "%s\\n\\n" "$SCREENS" + else + printf "%s\\n\\n" "No screens." + fi + unset SCREENS + fi + elif [[ -n "$STY" ]]; then + printf "%s\\n\\n" "Screen $STY, window $WINDOW." + else + TTY="$(tty | cut -d/ -f3-)" + SCREENS="$(screen -ls | command grep -F "${HOSTNAME%%.*}" | command grep -F "${TTY//\//-}")" + # This has to be an echo, not printf. + case "$(echo "${SCREENS:--n}" | wc -l)" in + 0) + # No screens found - start a new instance if on a tty. + # Disabled this because screens should be started on chuckie not the desktop/laptop. + # [[ "$TTY" == tty* ]] && screen + : + ;; + 1) + # Just one screen - reconnect if it's not dead. + if (( $(printf "%s" "$SCREENS" | command grep -c -F 'Dead') == 1 )); then + printf "%s:\\n" "Found dead screen for $TTY" + printf "%s\\n" "$SCREENS" | sed -e 's/^/ /g' + else + screen -dr "${TTY//\//-}.${HOSTNAME%%.*}" + fi + ;; + *) + # Multiple screens - output a list + printf "%s:\\n" "Multiple screens found for $TTY" + printf "%s\\n" "$SCREENS" | sed -e 's/^/ /g' + ;; + esac + fi + unset SCREENS TTY +} diff --git a/.bashrc b/.bashrc index 69c11c7..7dee364 100644 --- a/.bashrc +++ b/.bashrc @@ -1,150 +1,5 @@ #!/bin/bash - not strictly necessary, but helps nano with syntax highlighting. - -__prompt_git_status() { - # Generate a git branch/status prompt. - # Based on git-prompt.sh by Shawn O. Pearce . - # Arguments: - # $1 The printf format string for the prompt. Must include %s. - # Environment variables: - # GIT_PROMPT_SHOW_TYPE=1 Show type of repository (Bare, Shallow). - # GIT_PROMPT_SHOW_UPSTREAM=1 Show status of this repository compaired to upstream: - # ?? - No upstream set. - # == - Working tree is equal to upstream. - # <> - Divergent from upstream. - # >> or >x - Working tree is ahead of upstream (x = commits ahead when used with next option). - # << or 5 * - # Returns: 0 = Produced a prompt successfully. - # 1 = An error occured. - local BRANCH COUNT GIT_PROMPT GIT_PROMPT_MARKER_SET GIT_REPO_INFO IFS=$'\n' - - # Don't do anything if GIT_PROMPT_DISABLE is set. - [[ -v GIT_PROMPT_DISABLE ]] && return 0 - - # Bail out if there's no format argument given, or it doesn't contain %s - (( $# != 1 )) || [[ "$1" != *%s* ]] && return 1 - - # Get some repository information. - # shellcheck disable=SC2207 - GIT_REPO_INFO=( $( git rev-parse --is-bare-repository --is-shallow-repository --is-inside-git-dir --is-inside-work-tree 2>/dev/null) ) || return 1 - - # Generate the prompt. - if [[ "${GIT_REPO_INFO[2]}" == "true" ]]; then - # In the git directory, use a special branch marker. - GIT_PROMPT+="!GIT_DIR!" - elif [[ "${GIT_REPO_INFO[3]}" == "true" ]]; then - # In the working directory, generate the prompt. - # Add type markers. - [[ -n "$GIT_PROMPT_SHOW_TYPE" ]] && { - if [[ "${GIT_REPO_INFO[0]}" == "true" ]]; then - GIT_PROMPT+="B:" - elif [[ "${GIT_REPO_INFO[1]}" == "true" ]]; then - GIT_PROMPT+="S:" - fi - } - - # Add the branch or a no commits marker. - BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)" - if [[ "$BRANCH" == "HEAD" ]]; then - GIT_PROMPT+="?NO COMMITS?" - - # Output the prompt and escape early. - # shellcheck disable=SC2059 - printf -- "$1" "$GIT_PROMPT" - return 0 - else - GIT_PROMPT+="$BRANCH" - fi - - # Add upstream status. - [[ -n "$GIT_PROMPT_SHOW_UPSTREAM" ]] || [[ -n "$GIT_PROMPT_SHOW_UPSTREAM_EXTENDED" ]] && { - COUNT="$(git rev-list --count --left-right "${BRANCH:+refs/prefetch/remotes/origin/}${BRANCH:-@{upstream\}}...HEAD" 2>/dev/null | tr '[:blank:]' ' ')" - case "$COUNT" in - "") - # No upstream. - GIT_PROMPT+=" ??" - ;; - "0 0") - # Equal to upstream. - GIT_PROMPT+=" ==" - ;; - "0 "*) - # Ahead of upstream. - GIT_PROMPT+=" >" - if [[ -n "$GIT_PROMPT_SHOW_UPSTREAM_EXTENDED" ]]; then - # Show the number of the difference. - GIT_PROMPT+="${COUNT#0 }" - else - GIT_PROMPT+=">" - fi - ;; - *" 0") - # Behind upstream. - GIT_PROMPT+=" <" - if [[ -n "$GIT_PROMPT_SHOW_UPSTREAM_EXTENDED" ]]; then - # Show the number of the difference. - GIT_PROMPT+="${COUNT% 0}" - else - GIT_PROMPT+="<" - fi - ;; - *) - # Divergent from upstream. - GIT_PROMPT+=" <>" - ;; - esac - } - - # Add a marker if directory is ignored, there's unstaged files, uncommitted changes, untracked files or a stash. - [[ -n "$GIT_PROMPT_SHOW_IGNORED" ]] && git check-ignore . >/dev/null 2>&1 && { - GIT_PROMPT+=" !" - GIT_PROMPT_MARKER_SET=1 - } - [[ -z "$GIT_PROMPT_MARKER_SET" ]] && [[ -n "$GIT_PROMPT_SHOW_UNSTAGED" ]] && { - timeout --signal=KILL 2s git ls-files --modified --exclude-standard --directory --error-unmatch -- ':/*' >/dev/null 2>&1 - ERR=$? - if (( ERR == 124 )) || (( ERR == 137 )); then - GIT_PROMPT+=" _" - GIT_PROMPT_MARKER_SET=1 - elif (( ERR == 0 )); then - GIT_PROMPT+=" *" - GIT_PROMPT_MARKER_SET=1 - fi - } - [[ -z "$GIT_PROMPT_MARKER_SET" ]] && [[ -n "$GIT_PROMPT_SHOW_UNCOMMITTED" ]] && ! git diff --name-only --cached --exit-code >/dev/null 2>&1 && { - GIT_PROMPT+=" &" - GIT_PROMPT_MARKER_SET=1 - } - [[ -z "$GIT_PROMPT_MARKER_SET" ]] && [[ -n "$GIT_PROMPT_SHOW_UNTRACKED" ]] && { - timeout --signal=KILL 2s git ls-files git ls-files --others --exclude-standard --directory --error-unmatch -- ':/*' >/dev/null 2>&1 - ERR=$? - if (( ERR == 124 )) || (( ERR == 137 )); then - GIT_PROMPT+=" _" - GIT_PROMPT_MARKER_SET=1 - elif (( ERR == 0 )); then - GIT_PROMPT+=" +" - GIT_PROMPT_MARKER_SET=1 - fi - } - [[ -z "$GIT_PROMPT_MARKER_SET" ]] && [[ -n "$GIT_PROMPT_SHOW_STASH" ]] && git rev-parse --verify --quiet refs/stash >/dev/null && { - GIT_PROMPT+=" $" - GIT_PROMPT_MARKER_SET=1 - } - fi - - # Output the prompt. - # shellcheck disable=SC2059 - printf -- "$1" "$GIT_PROMPT" - - return 0 -} +# Bash specific configuration. __prompt_user_colour() { # Determine the colour of the username in the prompt. @@ -160,327 +15,8 @@ __prompt_user_colour() { return 0 } -__git_prompt_command() { - # Perform git actions. - # Environment variables: - # GIT_DISABLE_PROMPT_PREFETCH=1 Disable automatic 'prefetch' of upstream refs. - # This can also be disabled on a per repository basis using: - # git config ---local -replace-all --type bool script.DisablePromptPrefetch true - # Returns: 0 = Tasks completed successfully. - # 1 = An error occured. - local GIT_REPO_INFO LC_ALL="C" NOW REPO_TIMESTAMP TIMESTAMP_VAR - - # shellcheck disable=SC2207 - GIT_REPO_INFO=( $( git rev-parse --is-inside-work-tree --show-toplevel 2>/dev/null) ) || return 1 - - # Only process if in a work directory. - [[ "${GIT_REPO_INFO[0]}" == "true" ]] && { - # Run prefetch tasks if not disabled. - [[ -z "$GIT_DISABLE_PROMPT_PREFETCH" ]] && [[ "$(git config --local --get --type bool script.DisablePromptPrefetch 2>/dev/null)" != "true" ]] && { - timeout --signal=KILL 7s git maintenance run --task=prefetch 2>/dev/null || { - printf "\\033[1;31m%s\\033[0m\\n" "Git maintenance 'prefetch' task failed." >&2 - return 1 - } - } - - # The time now. - if [[ "$PLATFORM" == "Linux" ]]; then - NOW="$(date +'%s%3N')" - elif [[ "$PLATFORM" == "Darwin" ]]; then - NOW="$(perl -e 'use Time::HiRes; printf "%.3f", Time::HiRes::time();')" - NOW="${NOW/.}" - fi - - # Determine the timestamp variable name depending on bash version. - if (( BASH_VERSINFO[0] >= 4 )); then - TIMESTAMP_VAR="GIT_REPO_TIMESTAMP[${GIT_REPO_INFO[1]//[^[:alnum:]]/_}]" - else - # This is going to pollute the environment, but Darwin is a PITA. - TIMESTAMP_VAR="GIT_REPO_TIMESTAMP_${GIT_REPO_INFO[1]//[^[:alnum:]]/_}" - fi - - if [[ -n "${!TIMESTAMP_VAR}" ]]; then - # Monitor the git repo. - REPO_TIMESTAMP="$(git config --local --get --type int script.AutoMergeLast)" - (( ${!TIMESTAMP_VAR:-0} < REPO_TIMESTAMP )) && { - # Display message depending on status. - if [[ "$(git config --local --get --type bool script.AutoMergeSuccess)" == "true" ]]; then - printf "\\033[1;32m%s" "Git auto-merge succeeded for this repo." - if [[ "${GIT_REPO_INFO[1]}" == "$HOME" ]]; then - printf " %s\\033[0m\\n" "Re-source .bash* files." - else - printf "\\033[0m\\n" - fi - # Update the timestamp in the environment. - declare -g "$TIMESTAMP_VAR"="$NOW" - else - printf "\\033[1;31m%s\\033[0m\\n" "Git auto-merge failed for this repo - correct manually." >&2 - fi - } - else - # Just set the timestamp in the environment. - declare -g "$TIMESTAMP_VAR"="$NOW" - fi - } - - return 0 -} - -__nanorc_prompt_command() { - # Dynamically handle .nanorc file versions. - - hash nano >/dev/null 2>&1 && { - # shellcheck disable=SC2155 - local NANO_VER="$(command nano --version | awk '/version/ { print $4 }' | cut -d. -f1)" - - # Darwin specifc .nanorc version. - [[ "$PLATFORM" == "Darwin" ]] && NANO_VER="darwin" - - if [[ -f "$HOME/.nanorc-$NANO_VER" ]]; then - if (( NANO_VER <= 4 )); then - ( cd "$HOME" && ln -sf ".nanorc-$NANO_VER" ".nanorc" ) - else - # shellcheck disable=SC2139 - alias nano="nano -f \"$HOME/.nanorc-$NANO_VER\"" - fi - else - if (( NANO_VER <= 4 )); then - [[ -L "$HOME/.nanorc" ]] && rm -f "$HOME/.nanorc" - else - unalias nano 2>/dev/null - fi - printf "\\033[1;31m%s\\033[0m\\n" "No .nanorc for version '$NANO_VER'." >&2 - fi - } - - return 0 -} - -__ssh_agent_prompt_command() { - # If necessary, find and activate a new ssh agent socket before each prompt is displayed. - # Returns: 0 = All is good. - # 1 = An error occured. - local ERR - - if [[ -z "$SSH_AUTH_SOCK" ]]; then - ERR=2 - else - ssh-add -l >/dev/null 2>&1 - ERR=$? - fi - (( ERR == 2 )) && { - # Read alternative sockets from ~/.ssh/agents. - __read_ssh_agents || { - unset SSH_AUTH_SOCK - return 1 - } - - # Find a new socket to use. - if __find_ssh_agent_sock; then - printf "\\033[1;33m%s\\033[0m\\n" "Connected to existing ssh-agent socket." - sleep 0.5 - else - # Start a new agent. - eval "$(ssh-agent -s 2>/dev/null | grep -v 'echo'; printf "%s" "ERR=${PIPESTATUS[0]}")" - (( ERR > 0 )) && { - printf "\\033[1;31m%s\\033[0m\\n" "Failed to start new ssh-agent - continuing with no agent." - sleep 0.5 - return 1 - } - printf "\\033[1;32m%s\\033[0m\\n" "Started new ssh-agent." - __write_ssh_agents - sleep 0.5 - fi - } - - return 0 -} - -__find_ssh_agent_sock() { - # Find an *active* ssh agent socket. - # Returns: 0 = Found an active socket. - # 1 = Did not find a viable socket. - local I - - # Search the SSH_AUTH_SOCKS array for a viable socket. - for ((I = 0; I < ${#SSH_AUTH_SOCKS[@]}; I++)); do - [[ "${SSH_AUTH_SOCKS[$I]}" =~ ^[[:blank:]]*$ ]] && continue - SSH_AUTH_SOCK="${SSH_AUTH_SOCKS[$I]}" ssh-add -l >/dev/null 2>&1 - (( $? <= 1 )) && { - export SSH_AUTH_SOCK="${SSH_AUTH_SOCKS[$I]}" - return 0 - } - unset SSH_AUTH_SOCK - done - - return 1 -} - -__read_ssh_agents() { - # Read all the known ssh agent sockets into an array. - # Returns: 0 = Processed and read the agents file without issue. - # 1 = Error processing/reading the agents file. - local ERR I SOCK - - [[ ! -e "$HOME/.ssh/agents" ]] && touch "$HOME/.ssh/agents" - - # Lock the ~/.ssh/agents file. - if [[ "$PLATFORM" == "Linux" ]]; then - # Linux has 'flock', thankfully. - exec 9<"$HOME/.ssh/agents" && flock -E 10 -e -w 0.5 9 - ERR=$? - if (( ERR == 10 )); then - printf "\\033[1;31m%s\\033[0m\\n" "Failed to obtain lock on ~/.ssh/agents." >&2 - return 1 - elif (( ERR > 0 )); then - printf "\\033[1;31m%s\\033[0m\\n" "Flock usage error." >&2 - return 1 - fi - - # Make note of the mtime for use in write_ssh_agents. - SSH_AGENTS_MTIME="$(stat --format=%.9Y "$HOME/.ssh/agents")" - elif [[ "$PLATFORM" == "Darwin" ]]; then - # Do locking the sucky way on macOS. - for ((I = 0; I <= 5; I++)); do - if shlock -p "$$" -f "$HOME/.ssh/agents.lock"; then - exec 9<"$HOME/.ssh/agents" - # Make note of the mtime for use in write_ssh_agents. - SSH_AGENTS_MTIME="$(stat -f %Fm "$HOME/.ssh/agents")" - ERR=0 - break - else - ERR=1 - sleep 0.1 - fi - done - (( ERR != 0 )) && { - printf "\\033[1;31m%s\\033[0m\\n" "Failed to obtain lock on ~/.ssh/agents." >&2 - return 1 - } - else - printf "\\033[1;31m%s\\033[0m\\n" "File locking unsupported on '$PLATFORM'." >&2 - return 1 - fi - - # Read the socket list (bash v2+ compliant) - while read -u 9 -r SOCK; do - [[ -n "$SOCK" ]] && SSH_AUTH_SOCKS+=("$SOCK") - done - ERR=$? - - # Close the file descriptor (which on Linux releases the flock too). - exec 9<&- - - # On Darwin, release the lock on the file. - rm -f "$HOME/.ssh/agents.lock" - - # Remove the . in the mtime. - SSH_AGENTS_MTIME="${SSH_AGENTS_MTIME/\.}" - - # Error out if the data couldn't be read. - (( ERR != 0 )) && { - printf "\\033[1;31m%s\\033[0m\\n" "Failed to read ssh-agent socket list." >&2 - unset SSH_AUTH_SOCKS SSH_AGENTS_MTIME - return 1 - } - - return 0 -} - -__write_ssh_agents() { - # Write all unique ssh agent sockets into the ~/.ssh/agents file. - # Returns: 0 = Processed and wrote the agents file without issue. - # 1 = Error processing/writing the agents file. - # 2 = The SSH_AUTH_SOCKS array may be out of date as the agents file'a mtime has changed. - local ERR I J MTIME SOCKS - - # Add the current agent socket to the sockets array. - SSH_AUTH_SOCKS=("$SSH_AUTH_SOCK" "${SSH_AUTH_SOCKS[@]}") - - # Remove any duplicates from SSH_AUTH_SOCKS. - for ((I = 0; I < ${#SSH_AUTH_SOCKS[@]}; I++)); do - [[ -z "${SSH_AUTH_SOCKS[$I]}" ]] && continue - for ((J = 0; J < ${#SOCKS[@]}; J++)); do - [[ "${SSH_AUTH_SOCKS[$I]}" == "${SOCKS[$J]}" ]] && continue 2 - done - # Only add the socket if it's still viable. - SSH_AUTH_SOCK="${SSH_AUTH_SOCKS[$I]}" ssh-add -l >/dev/null 2>&1 - (( $? <= 1 )) && SOCKS+=("${SSH_AUTH_SOCKS[$I]}") - done - - # Lock the ~/.ssh/agents file. - if [[ "$PLATFORM" == "Linux" ]]; then - # Make sure SSH_AUTH_SOCKS has the most up to date data. - MTIME="$(stat --format=%.9Y "$HOME/.ssh/agents")" - (( ${MTIME/\.} > SSH_AGENTS_MTIME )) && return 2 - - # Lock the agents file. - exec 9<"$HOME/.ssh/agents" && flock -E 10 -e -w 0.5 9 - ERR=$? - if (( ERR == 10 )); then - printf "\\033[1;31m%s\\033[0m\\n" "Failed to obtain lock on ~/.ssh/agents." >&2 - return 1 - elif (( ERR > 0 )); then - printf "\\033[1;31m%s\\033[0m\\n" "Flock usage error." >&2 - return 1 - fi - elif [[ "$(uname -s)" == "Darwin" ]]; then - # Make sure SSH_AUTH_SOCKS has the most up to date data. - MTIME="$(stat -f %Fm "$HOME/.ssh/agents")" - (( ${MTIME/\.} > SSH_AGENTS_MTIME )) && return 2 - - # Do locking the sucky way on OSX. - for ((I = 0; I <= 5; I++)); do - if shlock -p "$$" -f "$HOME/.ssh/agents.lock"; then - exec 9<"$HOME/.ssh/agents" - ERR=0 - break - else - ERR=1 - sleep 0.1 - fi - done - (( ERR != 0 )) && { - printf "\\033[1;31m%s\\033[0m\\n" "Failed to obtain lock on ~/.ssh/agents." >&2 - return 1 - } - else - printf "\\033[1;31m%s\\033[0m\\n" "File locking unsupported on '$PLATFORM'." >&2 - return 1 - fi - - # Write the cleaned array to disk. - ERR=-1 - [[ -n "${SOCKS[*]}" ]] && { printf "%s\\n" "${SOCKS[@]}" >"$HOME/.ssh/agents" 2>/dev/null; ERR=$?; } - - # Release locks. - exec 9<&- - rm -f "$HOME/.ssh/agents.lock" - - # Error out if the data couldn't be written. - if (( ERR == -1 )); then - rm -f "$HOME/.ssh/agents" 2>/dev/null - elif (( ERR >= 1 )); then - rm -f "$HOME/.ssh/agents" 2>/dev/null - printf "\\033[1;31m%s\\033[0m\\n" "Failed to write ssh-agent socket list." >&2 - return 1 - fi - - return 0 -} - -imagebin() { - # Throw an image file into an imagebin. - - [[ -z "$1" ]] || [[ ! -e "$1" ]] && { - printf "%s: %s\\n" "Usage" "${FUNCNAME[0]} " >&2 - return 1 - } - curl -F file="@$1" https://imagebin.ca/upload.php | grep '^url:' | cut -d: -f2- -} - # Determine the platform being logged into. -PLATFORM="$(uname -s)" +PLATFORM="${PLATFORM:-$(uname -s)}" # Make bash a little more pleasent - these are valid for all versions. shopt -s cdspell checkhash checkwinsize cmdhist histappend no_empty_cmd_completion @@ -498,9 +34,6 @@ HISTTIMEFORMAT="%d/%m/%y %H:%M:%S " history -a history -r -# The commands to execute before the prompt is displayed. -PROMPT_COMMAND="__nanorc_prompt_command; __ssh_agent_prompt_command; __git_prompt_command" - # Git prompt options. GIT_PROMPT_SHOW_TYPE=1 GIT_PROMPT_SHOW_UPSTREAM=1 @@ -518,13 +51,11 @@ if (( BASH_VERSINFO[0] >= 4 )); then # Trim the path in the prompt. PROMPT_DIRTRIM=2 - # Coloured username + host + directory: - PS1='[\[\033[$(__prompt_user_colour)\]\u\[\033[0m\]@\[\033[1;33m\]\h\[\033[0m\]] \[\033[1;34m\]\w\[\033[0m\]$(__prompt_git_status "\[\\033[1;35m\] (%s)\[\\033[0m\]") ->' + # Coloured username + host + directory in the prompt. + PS1='[\[\033[$(__prompt_user_colour)\]\u\[\033[0m\]@\[\033[1;33m\]\h\[\033[0m\]] \[\033[1;34m\]\w\[\033[0m\]' else - # Set the prompts. - # Coloured username + host + directory: - # shellcheck disable=SC2154 - PS1='[\[\033[$(__prompt_user_colour)\]\u\[\033[0m\]@\[\033[1;33m\]\h\[\033[0m\]] \[\033[1;34m\]$(printf "%s" "${PWD/#$HOME/~}" | awk -F/ '\''{if (NF>3) {printf ".../" $(NF-1) "/" $NF} else {printf $0}}'\'')\[\033[0m\]$(__prompt_git_status "\[\\033[1;35m\] (%s)\[\\033[0m\]") ->' + # Coloured username + host + directory in the prompt. + PS1='[\[\033[$(__prompt_user_colour)\]\u\[\033[0m\]@\[\033[1;33m\]\h\[\033[0m\]] \[\033[1;34m\]$(printf "%s" "${PWD/#$HOME/~}" | awk -F/ '\''{if (NF>3) {printf ".../" $(NF-1) "/" $NF} else {printf $0}}'\'')\[\033[0m\]' fi # Set the debugger prompt. @@ -533,53 +64,7 @@ export PS4='+(\[\033[1;33m\]$?\[\033[0m\]) \[\033[1;34m\]${BASH_SOURCE##*/}\[\03 # Common aliases. hash bc >/dev/null 2>&1 && alias bc='bc -lq' -hash grep >/dev/null 2>&1 && { - alias egrep='grep -E --color=auto' - alias fgrep='grep -F --color=auto' - alias grep='grep --color=auto' -} - -# Auto start the ssh agent and add keys for scp/sftp/ssh. -__ssh_agent_prompt_command -hash scp ssh ssh-add >/dev/null 2>&1 && alias scp='_EXEC=scp ssh' -hash sftp ssh ssh-add >/dev/null 2>&1 && alias sftp='_EXEC=sftp ssh' -hash ssh ssh-add >/dev/null 2>&1 && ssh() { - local ERR - - if [[ -z "$SSH_AUTH_SOCK" ]]; then - ERR=2 - else - ssh-add -l >/dev/null 2>&1 - ERR=$? - fi - if (( ERR == 1 )); then - ssh-add - elif (( ERR == 2 )); then - __ssh_agent_prompt_command - ssh-add - fi - command "${_EXEC:-${FUNCNAME[0]}}" "$@" -} - -# Handle the ~/.gitconfig link. -if [[ -e "$HOME/.gitconfig-$USER@$HOSTNAME" ]]; then - FILENAME=".gitconfig-$USER@$HOSTNAME" -elif [[ -e "$HOME/.gitconfig-$USER@*.${HOSTNAME#*.}" ]]; then - FILENAME=".gitconfig-$USER@*.${HOSTNAME#*.}" -elif [[ -e "$HOME/.gitconfig-$USER@*" ]]; then - FILENAME=".gitconfig-$USER@*" -elif [[ -e "$HOME/.gitconfig-*@$HOSTNAME" ]]; then - FILENAME=".gitconfig-*@$HOSTNAME" -elif [[ -e "$HOME/.gitconfig-*.${HOSTNAME#*.}" ]]; then - FILENAME=".gitconfig-*.${HOSTNAME#*.}" -elif [[ -e "$HOME/.gitconfig-default" ]]; then - FILENAME=".gitconfig-default" -else - (cd "$HOME" && [[ -L ".gitconfig" ]] && rm -f ".gitconfig") - printf "%s: %s\\n" "${BASH_SOURCE##*/}" "failed to update .gitconfig symlink" >&2 -fi -[[ -n "$FILENAME" ]] && (cd "$HOME" && ln -sf "$FILENAME" ".gitconfig") -unset FILENAME +hash grep >/dev/null 2>&1 && { alias egrep='grep -E --color=auto'; alias fgrep='grep -F --color=auto'; alias grep='grep --color=auto'; } # Platform specific set up. if [[ "$PLATFORM" = "Linux" ]]; then @@ -587,7 +72,7 @@ if [[ "$PLATFORM" = "Linux" ]]; then hash ps grep >/dev/null 2>&1 && psgrep() { if [[ -n "$1" ]]; then # shellcheck disable=SC2009 - ps -auwwx | grep -E --color=always -- "(.*RSS.*|$1)" | grep -F -v '(.*RSS.*|' + ps -auwwx | command grep -E --color=always -- "(.*RSS.*|$1)" | command grep -F -v '(.*RSS.*|' else printf "%s: %s\\n" "Usage" "${FUNCNAME[0]} " >&2 return 1 @@ -604,23 +89,32 @@ if [[ "$PLATFORM" = "Linux" ]]; then hash last less >/dev/null 2>&1 && alias laston='last -a | less' hash ls >/dev/null 2>&1 && alias ls='ls -bFv --color=auto' hash minicom >/dev/null 2>&1 && alias minicom='minicom -m -c on' - hash mkpasswd >/dev/null 2>&1 && alias mkpasswd='mkpasswd -m sha512crypt' - hash mkpasswd >/dev/null 2>&1 && alias pwgen='mkpasswd -m sha512crypt' + hash mkpasswd >/dev/null 2>&1 && { alias mkpasswd='mkpasswd -m sha512crypt'; alias pwgen='mkpasswd -m sha512crypt'; } hash nc >/dev/null 2>&1 && alias pastebin='nc termbin.com 9999' hash pinfo >/dev/null 2>&1 && alias info='pinfo' hash ping >/dev/null 2>&1 && alias ping='ping -b' hash xclip >/dev/null 2>&1 && alias file2clip='DISPLAY=:0.0 xclip -selection clipboard' elif [[ "$PLATFORM" = "Darwin" ]]; then - # Darwin specific aliases (some dependant on macports) - [[ ! -e "/opt/local/libexec/gnubin/df" ]] && alias df='df -kP' - # shellcheck disable=SC2015 - [[ ! -e "/opt/local/libexec/gnubin//diff" ]] && alias diff='diff -u' || alias diff='diff --color=auto -u' + # Darwin specific aliases (some dependant on macports). + hash df >/dev/null 2>&1 && [[ ! -e "/opt/local/libexec/gnubin/df" ]] && alias df='df -kP' + hash diff >/dev/null 2>&1 && alias diff='diff -u' + [[ -e "/opt/local/libexec/gnubin//diff" ]] && alias diff='diff --color=auto -u' hash last less >/dev/null 2>&1 && alias laston='last | less' - # shellcheck disable=SC2015 - [[ ! -e "/opt/local/libexec/gnubin/ls" ]] && alias ls='ls -bFGO' || alias ls='ls -bFv --color=auto' + + hash ls >/dev/null 2>&1 && alias ls='ls -bFGO' + [[ -e "/opt/local/libexec/gnubin/ls" ]] && alias ls='ls -bFv --color=auto' [[ -e "/opt/local/libexec/gnubin/nc" ]] && alias pastebin='nc termbin.com 9999' [[ -e "/opt/local/bin/pinfo" ]] && alias info='pinfo' hash top >/dev/null 2>&1 && alias top='top -o cpu -S' else printf "%s: %s\\n" "${BASH_SOURCE##*/}" "unsupported platform: $PLATFORM" >&2 fi + +# Read in the shell configuration from .bashrc.d/*. +for FILE in "$HOME"/.bashrc.d/*; do + [[ -x "$FILE" ]] && source "$FILE" +done +unset FILE + +# Finalise the prompt. +PS1+=' ->' diff --git a/.bashrc.d/git b/.bashrc.d/git new file mode 100755 index 0000000..bb7cb91 --- /dev/null +++ b/.bashrc.d/git @@ -0,0 +1,220 @@ +#!/bin/bash - not strictly necessary, but helps nano with syntax highlighting. + +__git_prompt_command() { + # Perform git actions as part of PROMPT_COMMAND. + # Environment variables: + # GIT_PROMPT_COMMAND_DISABLE_PREFETCH=1 Disable automatic 'prefetch' of upstream refs. + # This can also be disabled on a per repository basis using: + # git config ---local -replace-all --type bool script.PromptCommandDisablePrefetch true + # Returns: 0 = Tasks completed successfully. + # 1 = An error occured. + local GIT_REPO_INFO LC_ALL="C" NOW REPO_TIMESTAMP TIMESTAMP_VAR + + # shellcheck disable=SC2207 + GIT_REPO_INFO=( $(git rev-parse --is-inside-work-tree --show-toplevel 2>/dev/null) ) || return 1 + + # Only process if in a work directory. + [[ "${GIT_REPO_INFO[0]}" == "true" ]] && { + # Run prefetch tasks if not disabled. + [[ -z "$GIT_PROMPT_COMMAND_DISABLE_PREFETCH" ]] && [[ "$(git config --local --get --type bool script.PromptCommandDisablePrefetch 2>/dev/null)" != "true" ]] && { + timeout --signal=KILL 7s git maintenance run --task=prefetch 2>/dev/null || { + printf "\\033[1;31m%s\\033[0m\\n" "Git maintenance 'prefetch' task failed." >&2 + return 1 + } + } + + # The time now. + if [[ "$PLATFORM" == "Linux" ]]; then + NOW="$(date +'%s%3N')" + elif [[ "$PLATFORM" == "Darwin" ]]; then + NOW="$(perl -e 'use Time::HiRes; printf "%.3f", Time::HiRes::time();')" + NOW="${NOW/.}" + fi + + # Determine the timestamp variable name depending on bash version. + if (( BASH_VERSINFO[0] >= 4 )); then + TIMESTAMP_VAR="GIT_REPO_TIMESTAMP[${GIT_REPO_INFO[1]//[^[:alnum:]]/_}]" + else + # This is going to pollute the environment, but Darwin is a PITA. + TIMESTAMP_VAR="GIT_REPO_TIMESTAMP_${GIT_REPO_INFO[1]//[^[:alnum:]]/_}" + fi + + if [[ -n "${!TIMESTAMP_VAR}" ]]; then + # Monitor the git repo. + REPO_TIMESTAMP="$(git config --local --get --type int script.AutoMergeLast)" + (( ${!TIMESTAMP_VAR:-0} < REPO_TIMESTAMP )) && { + # Display message depending on status. + if [[ "$(git config --local --get --type bool script.AutoMergeSuccess)" == "true" ]]; then + printf "\\033[1;32m%s" "Git auto-merge succeeded for this repo." + if [[ "${GIT_REPO_INFO[1]}" == "$HOME" ]]; then + printf " %s\\033[0m\\n" "Re-source .bash* files." + else + printf "\\033[0m\\n" + fi + # Update the timestamp in the environment. + declare -g "$TIMESTAMP_VAR"="$NOW" + else + printf "\\033[1;31m%s\\033[0m\\n" "Git auto-merge failed for this repo - correct manually." >&2 + fi + } + else + # Just set the timestamp in the environment. + declare -g "$TIMESTAMP_VAR"="$NOW" + fi + } + + return 0 +} + +__prompt_git_status() { + # Generate a git branch/status prompt. + # Based on git-prompt.sh by Shawn O. Pearce . + # Arguments: + # $1 The printf format string for the prompt. Must include %s. + # Environment variables: + # GIT_PROMPT_DISABLE=1 Disable git prompt processing completely in this shell. + # GIT_PROMPT_SHOW_TYPE=1 Show type of repository (Bare, Shallow). + # GIT_PROMPT_SHOW_UPSTREAM=1 Show status of this repository compaired to upstream: + # ?? - No upstream set. + # == - Working tree is equal to upstream. + # <> - Divergent from upstream. + # >> or >x - Working tree is ahead of upstream (x = commits ahead when used with next option). + # << or 5 * + # Returns: 0 = Produced a prompt successfully, or returned because GIT_PROMPT_DISABLE=1 was set. + # 1 = An error occured. + local BRANCH COUNT GIT_PROMPT GIT_PROMPT_MARKER_SET GIT_REPO_INFO IFS=$'\n' + + # Don't do anything if GIT_PROMPT_DISABLE is set. + [[ -v GIT_PROMPT_DISABLE ]] && return 0 + + # Bail out if there's no format argument given, or it doesn't contain %s + (( $# != 1 )) || [[ "$1" != *%s* ]] && return 1 + + # Get some repository information. + # shellcheck disable=SC2207 + GIT_REPO_INFO=( $(git rev-parse --is-bare-repository --is-shallow-repository --is-inside-git-dir --is-inside-work-tree 2>/dev/null) ) || return 1 + + # Generate the prompt. + if [[ "${GIT_REPO_INFO[2]}" == "true" ]]; then + # In the git directory, use a special branch marker. + GIT_PROMPT+="!GIT_DIR!" + elif [[ "${GIT_REPO_INFO[3]}" == "true" ]]; then + # In the working directory, generate the prompt. + # Add type markers. + [[ -n "$GIT_PROMPT_SHOW_TYPE" ]] && { + if [[ "${GIT_REPO_INFO[0]}" == "true" ]]; then + GIT_PROMPT+="B:" + elif [[ "${GIT_REPO_INFO[1]}" == "true" ]]; then + GIT_PROMPT+="S:" + fi + } + + # Add the branch or a no commits marker. + BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)" + if [[ "$BRANCH" == "HEAD" ]]; then + GIT_PROMPT+="?NO COMMITS?" + + # Output the prompt and escape early. + # shellcheck disable=SC2059 + printf -- "$1" "$GIT_PROMPT" + return 0 + else + GIT_PROMPT+="$BRANCH" + fi + + # Add upstream status. + [[ -n "$GIT_PROMPT_SHOW_UPSTREAM" ]] || [[ -n "$GIT_PROMPT_SHOW_UPSTREAM_EXTENDED" ]] && { + COUNT="$(git rev-list --count --left-right "${BRANCH:+refs/prefetch/remotes/origin/}${BRANCH:-@{upstream\}}...HEAD" 2>/dev/null | tr '[:blank:]' ' ')" + case "$COUNT" in + "") + # No upstream. + GIT_PROMPT+=" ??" + ;; + "0 0") + # Equal to upstream. + GIT_PROMPT+=" ==" + ;; + "0 "*) + # Ahead of upstream. + GIT_PROMPT+=" >" + if [[ -n "$GIT_PROMPT_SHOW_UPSTREAM_EXTENDED" ]]; then + # Show the number of the difference. + GIT_PROMPT+="${COUNT#0 }" + else + GIT_PROMPT+=">" + fi + ;; + *" 0") + # Behind upstream. + GIT_PROMPT+=" <" + if [[ -n "$GIT_PROMPT_SHOW_UPSTREAM_EXTENDED" ]]; then + # Show the number of the difference. + GIT_PROMPT+="${COUNT% 0}" + else + GIT_PROMPT+="<" + fi + ;; + *) + # Divergent from upstream. + GIT_PROMPT+=" <>" + ;; + esac + } + + # Add a marker if directory is ignored, there's unstaged files, uncommitted changes, untracked files or a stash. + [[ -n "$GIT_PROMPT_SHOW_IGNORED" ]] && git check-ignore . >/dev/null 2>&1 && { + GIT_PROMPT+=" !" + GIT_PROMPT_MARKER_SET=1 + } + [[ -z "$GIT_PROMPT_MARKER_SET" ]] && [[ -n "$GIT_PROMPT_SHOW_UNSTAGED" ]] && { + timeout --signal=KILL 2s git ls-files --modified --exclude-standard --directory --error-unmatch -- ':/*' >/dev/null 2>&1 + ERR=$? + if (( ERR == 124 )) || (( ERR == 137 )); then + GIT_PROMPT+=" _" + GIT_PROMPT_MARKER_SET=1 + elif (( ERR == 0 )); then + GIT_PROMPT+=" *" + GIT_PROMPT_MARKER_SET=1 + fi + } + [[ -z "$GIT_PROMPT_MARKER_SET" ]] && [[ -n "$GIT_PROMPT_SHOW_UNCOMMITTED" ]] && ! git diff --name-only --cached --exit-code >/dev/null 2>&1 && { + GIT_PROMPT+=" &" + GIT_PROMPT_MARKER_SET=1 + } + [[ -z "$GIT_PROMPT_MARKER_SET" ]] && [[ -n "$GIT_PROMPT_SHOW_UNTRACKED" ]] && { + timeout --signal=KILL 2s git ls-files git ls-files --others --exclude-standard --directory --error-unmatch -- ':/*' >/dev/null 2>&1 + ERR=$? + if (( ERR == 124 )) || (( ERR == 137 )); then + GIT_PROMPT+=" _" + GIT_PROMPT_MARKER_SET=1 + elif (( ERR == 0 )); then + GIT_PROMPT+=" +" + GIT_PROMPT_MARKER_SET=1 + fi + } + [[ -z "$GIT_PROMPT_MARKER_SET" ]] && [[ -n "$GIT_PROMPT_SHOW_STASH" ]] && git rev-parse --verify --quiet refs/stash >/dev/null && { + GIT_PROMPT+=" $" + GIT_PROMPT_MARKER_SET=1 + } + fi + + # Output the prompt. + # shellcheck disable=SC2059 + printf -- "$1" "$GIT_PROMPT" + + return 0 +} + +# Add the git prompt function call to PROMPT_COMMAND. +PROMPT_COMMAND+="__git_prompt_command;" + +# Add the git information to the prompt. +PS1+='$(__prompt_git_status "\[\\033[1;35m\] (%s)\[\\033[0m\]")' diff --git a/.bashrc.d/imagebin b/.bashrc.d/imagebin new file mode 100755 index 0000000..d19dd21 --- /dev/null +++ b/.bashrc.d/imagebin @@ -0,0 +1,11 @@ +#!/bin/bash - not strictly necessary, but helps nano with syntax highlighting. + +imagebin() { + # Throw an image file into imagebin. + + [[ -z "$1" ]] || [[ ! -e "$1" ]] && { + printf "%s: %s\\n" "Usage" "${FUNCNAME[0]} " >&2 + return 1 + } + curl -F file="@$1" https://imagebin.ca/upload.php | grep '^url:' | cut -d: -f2- +} diff --git a/.bashrc.d/nanorc b/.bashrc.d/nanorc new file mode 100755 index 0000000..20b45f0 --- /dev/null +++ b/.bashrc.d/nanorc @@ -0,0 +1,25 @@ +#!/bin/bash - not strictly necessary, but helps nano with syntax highlighting. + +__nanorc_prompt_command() { + # Dynamically handle .nanorc file versions. + + hash nano >/dev/null 2>&1 && { + # Darwin specifc .nanorc version. + [[ "$PLATFORM" == "Darwin" ]] && [[ "$(hash -t nano)" == "/usr/bin/nano" ]] && local NANO_VER="darwin" + # shellcheck disable=SC2155 + [[ -v NANO_VER ]] || local NANO_VER="$(command nano --version | awk '/version/ { print $4 }' | cut -d. -f1)" + + # Set the .nanorc symlink if it doesn't already point to the correct versioned file. + if [[ -f "$HOME/.nanorc-$NANO_VER" ]]; then + [[ "$(readlink "$HOME/.nanorc")" != ".nanorc-$NANO_VER" ]] && (cd "$HOME" && ln -sf ".nanorc-$NANO_VER" ".nanorc") + else + [[ -L "$HOME/.nanorc" ]] && rm -f "$HOME/.nanorc" + printf "\\033[1;31m%s\\033[0m\\n" "No .nanorc for version '$NANO_VER'." >&2 + fi + } + + return 0 +} + +# Add the nanorc prompt function call to PROMPT_COMMAND. +PROMPT_COMMAND+="__nanorc_prompt_command;" diff --git a/.bashrc.d/ssh b/.bashrc.d/ssh new file mode 100755 index 0000000..9ede404 --- /dev/null +++ b/.bashrc.d/ssh @@ -0,0 +1,243 @@ +#!/bin/bash - not strictly necessary, but helps nano with syntax highlighting. + +__find_ssh_agent_sock() { + # Find an *active* ssh agent socket. + # Returns: 0 = Found an active socket. + # 1 = Did not find a viable socket. + local I + + # Search the SSH_AUTH_SOCKS array for a viable socket. + for ((I = 0; I < ${#SSH_AUTH_SOCKS[@]}; I++)); do + [[ "${SSH_AUTH_SOCKS[$I]}" =~ ^[[:blank:]]*$ ]] && continue + SSH_AUTH_SOCK="${SSH_AUTH_SOCKS[$I]}" ssh-add -l >/dev/null 2>&1 + (( $? <= 1 )) && { + export SSH_AUTH_SOCK="${SSH_AUTH_SOCKS[$I]}" + return 0 + } + unset SSH_AUTH_SOCK + done + + return 1 +} + +__read_ssh_agents() { + # Read all the known ssh agent sockets into an array. + # Returns: 0 = Processed and read the agents file without issue. + # 1 = Error processing/reading the agents file. + local ERR I SOCK + + [[ ! -e "$HOME/.ssh/agents" ]] && touch "$HOME/.ssh/agents" + + # Lock the ~/.ssh/agents file. + if [[ "$PLATFORM" == "Linux" ]]; then + # Linux has 'flock', thankfully. + exec 9<"$HOME/.ssh/agents" && flock -E 10 -e -w 0.5 9 + ERR=$? + if (( ERR == 10 )); then + printf "\\033[1;31m%s\\033[0m\\n" "Failed to obtain lock on ~/.ssh/agents." >&2 + return 1 + elif (( ERR > 0 )); then + printf "\\033[1;31m%s\\033[0m\\n" "Flock usage error." >&2 + return 1 + fi + + # Make note of the mtime for use in write_ssh_agents. + SSH_AGENTS_MTIME="$(stat --format=%.9Y "$HOME/.ssh/agents")" + elif [[ "$PLATFORM" == "Darwin" ]]; then + # Do locking the sucky way on Darwin. + for ((I = 0; I <= 5; I++)); do + if shlock -p "$$" -f "$HOME/.ssh/agents.lock"; then + exec 9<"$HOME/.ssh/agents" + # Make note of the mtime for use in write_ssh_agents. + SSH_AGENTS_MTIME="$(stat -f %Fm "$HOME/.ssh/agents")" + ERR=0 + break + else + ERR=1 + sleep 0.1 + fi + done + (( ERR != 0 )) && { + printf "\\033[1;31m%s\\033[0m\\n" "Failed to obtain lock on ~/.ssh/agents." >&2 + return 1 + } + else + printf "\\033[1;31m%s\\033[0m\\n" "File locking unsupported on '$PLATFORM'." >&2 + return 1 + fi + + # Read the socket list (bash v2+ compliant) + while read -u 9 -r SOCK; do + [[ -n "$SOCK" ]] && SSH_AUTH_SOCKS+=("$SOCK") + done + ERR=$? + + # Close the file descriptor (which on Linux releases the flock too). + exec 9<&- + + # On Darwin, release the lock on the file. + rm -f "$HOME/.ssh/agents.lock" + + # Remove the . in the mtime. + SSH_AGENTS_MTIME="${SSH_AGENTS_MTIME/\.}" + + # Error out if the data couldn't be read. + (( ERR != 0 )) && { + printf "\\033[1;31m%s\\033[0m\\n" "Failed to read ssh-agent socket list." >&2 + unset SSH_AUTH_SOCKS SSH_AGENTS_MTIME + return 1 + } + + return 0 +} + +__ssh_agent_prompt_command() { + # If necessary, find and activate a new ssh agent socket before each prompt is displayed. + # Returns: 0 = All is good. + # 1 = An error occured. + local ERR + + if [[ -z "$SSH_AUTH_SOCK" ]]; then + ERR=2 + else + ssh-add -l >/dev/null 2>&1 + ERR=$? + fi + (( ERR == 2 )) && { + # Read alternative sockets from ~/.ssh/agents. + __read_ssh_agents || { + unset SSH_AUTH_SOCK + return 1 + } + + # Find a new socket to use. + if __find_ssh_agent_sock; then + printf "\\033[1;33m%s\\033[0m\\n" "Connected to existing ssh-agent socket." + sleep 0.5 + else + # Start a new agent. + eval "$(ssh-agent -s 2>/dev/null | grep -v 'echo'; printf "%s" "ERR=${PIPESTATUS[0]}")" + (( ERR > 0 )) && { + printf "\\033[1;31m%s\\033[0m\\n" "Failed to start new ssh-agent - continuing with no agent." + sleep 0.5 + return 1 + } + printf "\\033[1;32m%s\\033[0m\\n" "Started new ssh-agent." + __write_ssh_agents + (( $? == 2 )) && __read_ssh_agents && __write_ssh_agents + sleep 0.5 + fi + } + + return 0 +} + +__write_ssh_agents() { + # Write all unique ssh agent sockets into the ~/.ssh/agents file. + # Returns: 0 = Processed and wrote the agents file without issue. + # 1 = Error processing/writing the agents file. + # 2 = The SSH_AUTH_SOCKS array may be out of date as the agents file'a mtime has changed. + local ERR I J MTIME SOCKS + + # Add the current agent socket to the sockets array. + SSH_AUTH_SOCKS=("$SSH_AUTH_SOCK" "${SSH_AUTH_SOCKS[@]}") + + # Remove any duplicates from SSH_AUTH_SOCKS. + for ((I = 0; I < ${#SSH_AUTH_SOCKS[@]}; I++)); do + [[ -z "${SSH_AUTH_SOCKS[$I]}" ]] && continue + for ((J = 0; J < ${#SOCKS[@]}; J++)); do + [[ "${SSH_AUTH_SOCKS[$I]}" == "${SOCKS[$J]}" ]] && continue 2 + done + # Only add the socket if it's still viable. + SSH_AUTH_SOCK="${SSH_AUTH_SOCKS[$I]}" ssh-add -l >/dev/null 2>&1 + (( $? <= 1 )) && SOCKS+=("${SSH_AUTH_SOCKS[$I]}") + done + + # Lock the ~/.ssh/agents file. + if [[ "$PLATFORM" == "Linux" ]]; then + # Make sure SSH_AUTH_SOCKS has the most up to date data. + MTIME="$(stat --format=%.9Y "$HOME/.ssh/agents")" + (( ${MTIME/\.} > SSH_AGENTS_MTIME )) && return 2 + + # Lock the agents file. + exec 9<"$HOME/.ssh/agents" && flock -E 10 -e -w 0.5 9 + ERR=$? + if (( ERR == 10 )); then + printf "\\033[1;31m%s\\033[0m\\n" "Failed to obtain lock on ~/.ssh/agents." >&2 + return 1 + elif (( ERR > 0 )); then + printf "\\033[1;31m%s\\033[0m\\n" "Flock usage error." >&2 + return 1 + fi + elif [[ "$(uname -s)" == "Darwin" ]]; then + # Make sure SSH_AUTH_SOCKS has the most up to date data. + MTIME="$(stat -f %Fm "$HOME/.ssh/agents")" + (( ${MTIME/\.} > SSH_AGENTS_MTIME )) && return 2 + + # Do locking the sucky way on Darwin. + for ((I = 0; I <= 5; I++)); do + if shlock -p "$$" -f "$HOME/.ssh/agents.lock"; then + exec 9<"$HOME/.ssh/agents" + ERR=0 + break + else + ERR=1 + sleep 0.1 + fi + done + (( ERR != 0 )) && { + printf "\\033[1;31m%s\\033[0m\\n" "Failed to obtain lock on ~/.ssh/agents." >&2 + return 1 + } + else + printf "\\033[1;31m%s\\033[0m\\n" "File locking unsupported on '$PLATFORM'." >&2 + return 1 + fi + + # Write the cleaned array to disk. + ERR=-1 + [[ -n "${SOCKS[*]}" ]] && { printf "%s\\n" "${SOCKS[@]}" >"$HOME/.ssh/agents" 2>/dev/null; ERR=$?; } + + # Release locks. + exec 9<&- + rm -f "$HOME/.ssh/agents.lock" + + # Error out if the data couldn't be written. + if (( ERR == -1 )); then + rm -f "$HOME/.ssh/agents" 2>/dev/null + elif (( ERR >= 1 )); then + rm -f "$HOME/.ssh/agents" 2>/dev/null + printf "\\033[1;31m%s\\033[0m\\n" "Failed to write ssh-agent socket list." >&2 + return 1 + fi + + return 0 +} + +# Add the ssh-agent prompt function call to PROMPT_COMMAND. +PROMPT_COMMAND+="__ssh_agent_prompt_command;" + +# Auto start the ssh agent. +__ssh_agent_prompt_command + +# Prompt to add keys to ssh-agent with scp/sftp/ssh commands. +hash scp ssh ssh-add >/dev/null 2>&1 && alias scp='_SSHEXEC=scp ssh' +hash sftp ssh ssh-add >/dev/null 2>&1 && alias sftp='_SSHEXEC=sftp ssh' +hash ssh ssh-add >/dev/null 2>&1 && ssh() { + local ERR + + if [[ -z "$SSH_AUTH_SOCK" ]]; then + ERR=2 + else + ssh-add -l >/dev/null 2>&1 + ERR=$? + fi + if (( ERR == 1 )); then + ssh-add + elif (( ERR == 2 )); then + __ssh_agent_prompt_command + ssh-add + fi + command "${_SSHEXEC:-ssh}" "$@" + unset _SSHEXEC +} diff --git a/.gitattributesdb b/.gitattributesdb index db934a1..7cfdf8b 100644 --- a/.gitattributesdb +++ b/.gitattributesdb @@ -2,8 +2,15 @@ # Do not manually edit this file - any changes will be overwritten. LmJhc2hfbG9nb3V0 1724604115.707184525 1724604120.844974739 root:root 0644 - - -LmJhc2hfcHJvZmlsZQ== 1724603117.381212400 1699054853.073970225 tadgy:users 0644 - - -LmJhc2hyYw== 1724529154.441277405 1724462955.427818700 tadgy:users 0644 - - +LmJhc2hfcHJvZmlsZQ== 1724603117.381212400 1724614528.550248801 tadgy:users 0644 - - +LmJhc2hfcHJvZmlsZS5kL2dpdGNvbmZpZw== 1724529135.866494652 1724614528.559248434 tadgy:users 0644 - - +LmJhc2hfcHJvZmlsZS5kL3Blcmw1 1724603434.952337213 1724614528.559248434 tadgy:users 0755 - - +LmJhc2hfcHJvZmlsZS5kL3NjcmVlbg== 1724527600.768251861 1724614528.560248394 tadgy:users 0755 - - +LmJhc2hyYw== 1724529154.441277405 1724614528.551248760 tadgy:users 0644 - - +LmJhc2hyYy5kL2dpdA== 1724523575.106991980 1724614528.560248394 tadgy:users 0755 - - +LmJhc2hyYy5kL2ltYWdlYmlu 1724522976.223364495 1724614528.561248352 tadgy:users 0755 - - +LmJhc2hyYy5kL25hbm9yYw== 1724524202.806528473 1724614528.561248352 tadgy:users 0755 - - +LmJhc2hyYy5kL3NzaA== 1724522806.719454222 1724614528.561248352 tadgy:users 0755 - - LmNvbmZpZy8uZ2l0aWdub3Jl 1724603481.601075802 1724603481.607075557 root:root 0644 - - LmNvbmZpZy9iYXQvY29uZmln 1724463022.797001518 1724611589.222141848 tadgy:users 0644 - - LmNvbmZpZy9odG9wL2h0b3ByYw== 1699313773.313100954 1714866668.270612916 root:root 0644 - - @@ -64,7 +71,7 @@ LmVzeGNsaS1lc3gwLmdwZw== 1699313780.070811396 1724611854.494309944 root:root 060 LmVzeGNsaS1lc3gxLmdwZw== 1699313780.070811396 1724611854.494309944 root:root 0600 - - LmVzeGNsaS1lc3gyLmdwZw== 1699313780.070811396 1724611854.494309944 root:root 0600 - - LmVzeGNsaS1lc3gzLmdwZw== 1699313780.070811396 1724611854.494309944 root:root 0600 - - -LmdpdA== 1724614502.678302444 1714913373.953294495 root:root 0700 - - +LmdpdA== 1724614566.680695904 1714913373.953294495 root:root 0700 - - LmdudXBnL3JhbmRvbV9zZWVk 1699554348.493632349 1699554331.854331666 root:root 0600 - - LnNzaC9hZ2VudHM= 1724453355.468370739 1724453355.429372331 root:root 0600 - - LnNzaC9rbm93bl9ob3N0cw== 1721585018.655169717 1721585031.292661546 root:root 0600 - -