# If non-interactive shell, return immediately if [[ $- != *i* ]] ; then return fi ## This script provides many utility functions and aliases, and also # configures a few things on the basis of user-provided variables. # # This script is intended to be saved as ~/.init.user.sh, from where # it can be symlinked to ~/.bashrc or ~/.zshrc to use as the primary # initialization script for a shell. # # This script sources ~/.init.env.sh if it exists, and uses the # variables that may optionally be defined there to set up some # a few conveniences related to ssh, gpg, and CVS. # # After setting up these things, it defines many functions and aliases # that a user might find useful, and then finally, it sources the # ~/.init.custom.sh file if it exists, where you can define whatever # you want and reuse any of the functions that are defined in this # script. # # In order to take advantage of all functionality, you must create a # file at ~/.init.env.sh, in which you enter some variables that # this script uses for various things. # # The special variables in that file that enable additional functionality # are as follows (see below the list for more extensive descriptions # of each): # # DEBUG_SH enables debug output of this script and its functions; # USE_KEYCHAIN enables keychain-related functionality; # SSH_KEYFILE specifies an SSH private key file; # SSH_KEY_APPS specifies which apps to create SSH aliases for; # CVS_PATH specifies a local path to a CVS repository; # CVS_HOST specifies a hostname for the CVS repository; # USER_LANG specifies default language and encoding of text files; # USER_LINGUAS specifies languages for which gettext-based programs # should install message translation files; # VAR_OVERWRITE specifies whether to overwrite a variable that has # already been set by some other script or leave it as is. # # USE_KEYCHAIN: # If you set this to 1, then keychain will be started, which results # in ssh-agent and gpg-agent being started if they have not already # been started (or restarted if started but not valid). # # SSH_KEYFILE: # This should specify a full path to an SSH private key, if set. # If you specify a key file and also set SSH_KEY_APPS, then # the script will define aliases for each app so that every time # you invoke the app, the script checks to see if the key has been # added to ssh-agent, and adds it if it has not or if it has expired # and been removed by ssh-agent. # The only user-visible difference in using the alias is that if your # key file specifies a non-empty passphrase (as it should), then you'll # be prompted for your passphrase if it was necessary to add the key. # # VAR_OVERWRITE: # If set to 1 or "1", then in the case that you specify some variable # that would be used to export an environment variable (like CVSROOT) # and if that variable has been previously set by something else, then # this script will overwrite that variable. If not set or set to any # other value, then this script will not alter the variable. This # applies to all the variables that are described after VAR_OVERWRITE. # # CVS_PATH: # If you set this, it should specify a path to a CVS repository. This # is used, in conjunction with CVS_HOST, to create and export a CVSROOT # variable (if not already or set or if VAR_OVERWRITE is set). # The CVSROOT that is set depends on which host the script is run on: # if you run it on the same host as CVS_HOST, the CVSROOT will just be # be the local path. If you run it on any other host, the CVSROOT # will be an ext path that accesses the CVS repository over SSH. # # CVS_HOST: # The hostname of the CVS repository, if any. If this is set, it is used # in conjunction with the CVS_PATH variable to create a local CVSROOT on # the CVS_HOST machine and to set an ext path that accesses CVS over SSH # on all other machines. # # USER_LANG: # This should be something like "en_US.UTF-8", and is used to export the # LANG and LC_ALL variables that C compilers and other languages use, # as well as LANGUAGE (not sure what uses this) and GDM_LANG (Gnome). # # USER_LINGUAS: # This can be set to a space-delimited list of 2-letter language codes # representing the languages that gettext-based programs should install # message translation files for. # # Depending on the shell, try to enable some nice options case "$0" in bash) shopt -s checkwinsize shopt -s cdable_vars shopt -s extglob shopt -s histappend # source default bashrc if it exists test -f /etc/bash/bashrc && source /etc/bash/bashrc # activate bash-completion test -f /etc/profile.d/bash-completion && source /etc/profile.d/bash-completion ;; zsh) setopt extendedglob test -f /etc/zsh/zprofile && source /etc/zsh/zprofile complist autoload -U compinit promptinit ZLS_COLORS=$LS_COLORS compinit promptinit #prompt gentoo # enables cache for completions: zstyle ':completion::complete:*' use-cache 1 ;; *) # :-( ;; esac ## Some colors for use in functions below # Turns on reverse background effect, where background is a solid block REV_ON="$(test -n "$TERM" && tput smso)" # Turns off STAND_OUT effect. REV_OFF="$(test -n "$TERM" && tput rmso)" BOLD='\033[1m' red='033[0;31m' RED='\033[1;31m' blue='\033[0;34m' BLUE='\033[1;34m' cyan='\033[0;36m' CYAN='\033[1;36m' green='\e[1;32m' # Return to normal color format. NCOL='\033[0m' #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # ALIASES # #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ alias ..='cd ..' alias c="clear" alias confcat="sed -e 's/#.*//;/^\s*$/d'" ## Show disk usage with human friendly sizes in sorted order. ## From: ## http://www.earthinfo.org/linux-disk-usage-sorted-by-size-and-human-readable/ function duf { du -sk "$@" | sort -n | while read size fname; do for unit in k M G T P E Z Y; do if [ $size -lt 1024 ]; then echo -e "${size}${unit}\t${fname}"; break; fi; size=$((size/1024)); done; done } alias ew='emacs -nw' alias elinks="TERM=xterm-256color; elinks" alias env="\env | \sort | \grep --color=auto -P '^[A-Z_]+'" alias envg="\env | \sort | \grep --color=auto" alias envgi="\env | \sort | \grep --color=auto -i" function futurama() { curl -Is slashdot.org | egrep '^X-(F|B|L)' | cut -d \- -f 2; } ## The following functions are defined here because you would expect ## to see them as aliases. ## ## They are defined as functions rather than aliases so that each time ## the command is given, the current value of CO may be used ## to determine whether color highlighting of a match occurs. If these ## were defined as aliases, we would be limited to specifying a value ## when this file is initially sourced and changing it later. ## ## A value of `always` is best for interactive use, since even with the ## `auto` setting, color codes won't be sent to piped programs like `less`. ## But if set to `always`, piping grep output to another grep process ## will result in color codes being output, which may interfere with ## matching in the next process. ## ## Therefore, we define the following as functions rather than aliases, ## and you can easily turn coloring on and off by tweaking the CO ## variable. E.g., to temporarily set to auto for a piped search, do ## `CO=auto g '^asdf' /tmp/foobar`. ## ## Mnemomic for abbreviations: ## * base name: 'g' for extended regex syntax; 'gp' for perl regex syntax ## * options ## * with line numbers: append 'n' ## * case insensitive: append 'i' ## * with line numbers and case-insenstive: append 'ni' ## ## You may specify other arguments to grep by including them before ## the pattern and filenames. If your pattern starts with a hyphen '-', ## make sure to use the -e option, which prevents the following arguments ## from being interpreted as command flags. # extended grep syntax function g() { \grep --color=$CO -E $*; } # ... with line numbers function gn() { \grep --color=$CO -En $*; } # ... case-insensitive function gi() { \grep --color=$CO -iE $*; } # ... with line numbers and case-insensitive function gni() { \grep --color=$CO -iEn $*; } # perl regex syntax function gp() { \grep --color=$CO -P $*; } # ... with line numbers function gpn() { \grep --color=$CO -Pn $*; } # ... case-insensitive function gpi() { \grep --color=$CO -iP $*; } # ... with line numbers and case-insensitive function gpni() { \grep --color=$CO -iPn $*; } # word lookup: case-insenstive search of std dict function wl() { gi $* ${DICT_FILE}; } alias h="history | grep -i" alias l="launch_exe" alias la="\ls --color=auto -a -F" alias ll="\ls --color=auto -l -F" alias lla="\ls --color=auto -a -l -F" alias ls="\ls --color=auto -F" alias lsd="\ls --color=auto -d */" alias lstr="\ls --color=auto -ltrF" alias mplayer='mplayer -af scaletempo' alias mountcd="mount -t iso9660 -r /dev/cdrom /mnt/cdrom" alias p="while true; do ping google.com; sleep 1; done" alias pyc="python -c" alias psg=ps_grep alias psgw=ps_grep_watch # redo last command and send output to `less` alias redol="!! | less" alias sb="source ~/.init.user.sh" alias vi="vim -p" alias vim="vim -p" alias gvim="gvim -p" alias usage="top -b -n 1 | head -n 5 | tail -n 3" alias xmlf="xmllint --format" alias xslt="xsltproc" alias xsd="xmllint --schema" alias youtube-dl="youtube-dl -t -c" #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # FUNCTION DEFINITIONS # #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ### # Utility functions for things like verifying that a file or directory # exists and is executable, printing error messages to stderr, and # launching processes that persist even if the shell that launches them dies. # # Use printf to display an message on stderr, using the 1st argument # as the prefix for the message (e.g., "ERROR" or "INFO") and the # 2nd argument as the message template string; any remaining arguments # are consider to be printf args to be interpolated into the message # template string, and should match accordingly. print_typed_msg() { if [ $# -gt 1 ]; then msg_type="$1" msg="$2" shift 2 printf "${msg_type}: ${msg}\n" $@ 1>&2 fi } # If the DEBUG_SH environment variable is set to an integer greater than # zero, print a debug message to std err, using the first argument # as the message template and any remaining arguments as values to be # passed to printf as args to be interpolated. The message is prefixed by # DEBUG:, and a newline is automatically added to the first argument. debug() { if [ "${DEBUG_SH:-0}" -ne "0" ]; then msg="$1" shift print_typed_msg "DEBUG" "$msg" $@ fi } # Use printf to display an error message to stderr, using first argument # as the message template and any remaining arguments as values to be # passed to printf as args to be interpolated. The message is prefixed by # ERROR:, and a newline is automatically added to the first argument. err() { msg="$1" shift print_typed_msg "ERROR" "$msg" $@ } # Use printf to display a warning message to stderr, using first argument # as the message template and any remaining arguments as values to be # passed to printf as args to be interpolated. The message is prefixed by # WARNING:, and a newline is automatically added to the first argument. warn() { msg="$1" shift print_typed_msg "WARNING" "$msg" $@ } # Use printf to display an info message to stderr, using first argument # as the message template and any remaining arguments as values to be # passed to printf as args to be interpolated. The message is prefixed by # INFO:, and a newline is automatically added to the first argument. inf() { msg="$1" shift print_typed_msg "INFO" "$msg" $@ } # Expand a path that may begin with a tilde. expand_path() { echo $(eval echo $1) } # Verify that each arg represents an executable on PATH of current user, # printing an error message if not and returning with failure status if not. verify_exe() { state=0 for arg in $@ do if ! eval "which ${arg}" &> /dev/null; then # no need to check if executable additionally, since which # only succeeds if its executable and on path. err "${arg} not on PATH or not executable." state=1 fi done return ${state} } # Verify that an executable given by $1 is installed, using the customized # error message in $2 to report if not. The purpose of this, as compared # to the more generic verify_exe, is that a custom error message could # direct the user on what they need to install in order to get the # missing executable, as opposed to unhelpfully reporting its mere absence. verify_installed() { if ! which $1 &> /dev/null ; then err "$2" return 1 fi return 0 } # Verify that each arg represents a directory that the current user # can cd into (requires executable privilege on directory), performing # tilde expansion on any string argument when necessary. If verification # fails, an error message is printed to stderr and the function returns # with non-zero status. verify_dir() { state=0 for arg do if [ ! -x $(expand_path "${arg}") ]; then err "${arg} directory does not exist or has wrong permissions." state=1 fi done return ${state} } # Launch an executable in the background in such a manner that the process # will not be terminated if the shell that launched it is terminated. # The launched program will not read anything from stdin, and all output # will be sent to /tmp/$USERNAME/$1.out. Any parameters after the 2nd # are passed as parameters to the executable. launch_exe() { if [ $# -lt 1 ]; then err "Usage: launch_exe executable" return 1 fi exe=$1 shift out_dir=/tmp/$USERNAME test -d $out_dir || mkdir $out_dir verify_exe $exe && ($exe $* $out_dir/${exe}.out &) } # Launch an executable as in launch_exe from the directory given as the # 2nd parameter. Any parameters after the 2nd are passed as parameters to # the executable. launch_exe_in_dir() { if [ $# -lt 2 ]; then err "Usage: launch_exe_in_dir executable dirrectory [executable_arg]*" return 1 fi exe=$1 dir=$(expand_path "$2") shift 2 verify_exe $exe && verify_dir $dir && cd $dir && launch_exe $exe $* } # Takes a delimiter as arg 1 and a string as arg 2, and echoes to stdout # a tokenized version of the string, with each token on a separate line. # For example, tokenize " " "1 2 3" would output each number on a separate # line. tokenize() { if [ $# -ne 2 ]; then erro "Usage: tokenize delim str" return 1 fi for token in ${2%%$1}; do echo "$token" done } # Find files in current directory or a subdirectory (recursively) that # have a name containing the 1st arg as a substring. E.g., executing # ff bar would find files with names like bar, foo_bar, bar.txt, etc.. ff() { find . -name "*$1*" } # Find files in current directory or a subdirectory (recursively) that # match as in ff and then execute the 2nd arg on each file. E.g., # executing fe bar md5sum would print the md5 digest of all files that # match the first argument. fe() { find . -name "*$1*" -exec $2 {} \; } # Find a regular expression given as the 1st arg in files # matching any arg after the 1st (matching like the ff function), or # in any file in the current directory or its subdirectories if no 2nd arg is # given, and print each line containing the pattern, highlighting the # matched part of each line found. fstr() { if [ "$#" -lt 2 ]; then echo 'Usage: fstr "pattern" [filenames]' return 1; fi if [ -n "$2" ]; then name="*$2*" else name="*" fi find . -type f -name "$name" -print | \ xargs egrep -Esin "$1" | \ sed -r "s/($1)/${REV_ON}\1${REV_OFF}/gI" } # Determine timeout for passphrases based on user and host. # TODO: remove hardcoded stuff from here and have it sourced # from elsewhere so this may be used by others without having to alter. passphrase_timeout() { case "$(whoami)@$(hostname)" in *@protempore.net) echo 0 return 0 ;; root@*|*@turing|*@newton) echo $((60 * 4)) return 0 ;; *@gould) echo $((60 * 24)) return 0 ;; *) echo 60 # use 1 hour for unknown, but print a warning warn "Unknown username@hostname: $(whoami)@$(hostname)" return 1 ;; esac } # Ensure that a key has been registered with the ssh-agent in the # current environment, and add the key if necessary. # This function works well with keychain # , and allows you to easily # provide transparent adding of keys by creating an alias for things # that require ssh pubkey usage. For example, # alias darcs="ensure_key ~/.ssh/id_rsa && darcs" # After defining the alias, you can call darcs as usual, and if # the key has not been added yet, it will prompt you for the key # and its passphrase (and will not prompt if previously added and you are # still within the timeout period with which ssh-agent was configured). ensure_key() { if [ $# -ne 1 ]; then err "ensure_key must be called with path to key as argument." return 1 fi # Determine status of ssh-agent ssh-add -l &> /dev/null agent_status=$? case $agent_status in 0) # Running and at least one key has already been added fingerprint="$(ssh-keygen -l -f "$1" | awk '{print $2}')" if ssh-add -l | grep "$fingerprint" &> /dev/null ; then # The target key has been added, so nothing to be done return 0 else # Target key hasn't been added yet, so add it ssh-add "$1" return $? fi ;; 1) # Running with no keys added yet, so add key: ssh-add "$1" return $? ;; *) # 2 indicates not running, and we treat any other error status # the same err "ssh-agent is not running or not visible from this shell." return 1 ;; esac } # For each filepath argument, display the md5sum for the corresponding file. md5sums() { err_msg="md5sum is not installed. It is provided by GNU Coreutils, \ and is downloadable from http://www.gnu.org/software/coreutils/" verify_installed md5sum md5sum err_msg test $? -eq 0 || return 1 for res do if [ -f "$res" ]; then md5sum $res fi done } # Grep (basic) through ps output, showing matching lines. ps_grep() { ps auxww | egrep --color=always -i $@ | grep -v grep | more; } # Watch ps output for a given string, which is useful for waiting # for a process to terminate. ps_grep_watch() { watch -n 0.5 "ps auxww | egrep -i $@ | grep -v grep"; } # Bounce a service by stopping it, waiting for a couple of seconds, and # starting it again. bounce() { init_script=/etc/init.d/$1 verify_exe ${init_script} || return 1 # Do this carefully so that we return with failure if we can't stop # successfully but are still able to handle wait failing with # return code 127 (indicating the process stopped very quickly # and wait knows nothing about a process with that pid). ${init_script} stop || return 1 wait $! sleep 1 ${init_script} start } # Restart wireless after killing existing wpa_supplicant (if any). # Must be run as root or via sudo. wireless_restart() { killall wpa_supplicant sleep 1 /root/wireless.up.sh && sleep 1 && p } wireless_reset() { /root/wireless.reset.sh } # Insert all kernel modules matching regular expression given as first # argument. User must be root or have sudo privileges for /sbin/modprobe. modprobe_all() { for module in $(sudo /sbin/modprobe -l | grep $1); do module_name=$(basename "${module}" .ko); echo "${module_name}"; sudo /sbin/modprobe "${module_name}"; done return $?; } # Prints the IP address of the first interface that has a non-localhost IP # adress, returning 0 if successful and 1 if not. this_ip() { ip_pat="addr:([0-9]+\.){3}[0-9]+" for ip in $(/sbin/ifconfig -a | egrep -E -o $ip_pat | cut -d: -f2); do if [ "127." != $(expr substr $ip 1 4) ]; then echo $ip; return 0; fi done return 1; } # Prints information about the host to which you are connected, such as # hostname and IP address, current users, uptime, and memory info. ii() { ip=$(this_ip) echo -e "\nLogged in to ${RED}$HOSTNAME${NCOL} (${ip:-\"Not connected\"})" echo -e "\n${RED}Additional information:${NCOL} " ; uname -a echo -e "\n${RED}Users logged on:${NCOL} " ; w -h echo -e "\n${RED}Current date:${NCOL} " ; date echo -e "\n${RED}Machine stats:${NCOL} " ; uptime echo -e "\n${RED}Memory stats:${NCOL} " ; free } # show the description of USE flags (Gentoo-specific); showuse () { for flag in $@ do grep -h $flag /usr/portage/profiles/use.* done } # Page through a file given as $1 using vim. vmore() { if [ $# -eq 0 ]; then err "Usage: vmore filepath+" return 1 fi if [ ! -f ~/.vimrc.more ]; then err "set ~/.vimrc.more: see http://vim.wikia.com/wiki/VimTip121" return 1 fi for path in $@; do vim -u ~/.vimrc.more $path done return $? } # Page through a file read from std in. vmori() { vmore - } # Remind the user of a message (all args after 1st) in $1 minutes. remind() { if [ $# -lt 2 ]; then err "Usage: ./remind.sh \"Remind Message...\"" return 1 fi minutes=$1 shift echo "$(date): Reminder set for $minutes minutes" # launch_exe messes up the text arg, so we launch manually (sleep $((60 * $minutes)) < /dev/null &> /dev/null && \ zenity --info --title="REMINDER" --text="$*" /dev/null &) } # Resize one or more images to the percentage given by $1; all # arguments after the 1st are taken as filenames of images to be resized. # The percentage argument should be an integer from 1 to 99. # Requires ImageMagick to be installed and on the $PATH. resize_images() { verify_installed convert "You must install ImageMagick to use this function." test $? -gt 0 && return 1 if [[ $# -lt 2 ]]; then err "Usage: resize filename+" return 1 fi percent="${1}" percent_arg="${percent}x${percent}%" shift 1 status=0 for file in $*; do if [ ! -f "${file}" ]; then printf "File does not exist: %s\n" "${file}" status=1 continue fi dir="$(dirname ${file})" filename_and_ext="$(basename ${file})" filename="${filename_and_ext%.*}" ext="${filename_and_ext##*.}" new_filename_and_ext="${dir}/${filename}_percent-${percent}.${ext}" convert -scale ${percent_arg} "${file}" "${new_filename_and_ext}" done return $status } # Get the pids of processes that have $1 as part of the command by performing # grep on the command and returning the pids of all processes that have # a command that is matched by the pattern given as $1 (basix regex syntax). getpid() { echo "$(ps -eo pid,command | grep $1 | grep -v grep | egrep -o '^ ?[0-9]+')" 2> /dev/null } # Suspend all processes that have a command string that is matched by a # basic regex pattern given in $*. suspend() { for pat in $@; do pids="$(getpid ${pat})" for pid in ${pids}; do echo "suspending ${pat} ${pid}..." kill -STOP ${pid} done done } # Resume all processes that have a command string that is matched by a # basic regex pattern given in $*. resume() { for pat in $@; do pids="$(getpid ${pat})" for pid in ${pids}; do echo "resuming ${pat} ${pid}..." kill -CONT ${pid} done done } # Shows how arg expansion works for "$*" and "$@" (with and # without double quotes in the function) using the given args # to the function. howto_args() { if [[ -z "$*" ]]; then echo "Usage: howto_args 'test arg 1' arg2 \"test arg 3\"" return fi echo -e '--------------' echo -e '"$@" expansion' echo -e '--------------' for arg in "$@"; do echo ${arg} done echo -e '\n--------------' echo -e '"$*" expansion' echo -e '--------------' for arg in "$*"; do echo ${arg} done echo -e '\n------------' echo -e '$@ expansion' echo -e '------------' for arg in $@; do echo ${arg} done echo -e '\n------------' echo -e '$* expansion' echo -e '------------' for arg in $*; do echo ${arg} done } # Use Portage's ebuild to download and unpack source for the latest # version of a package given the package spec. It unpacks it into # a userdirectory under ~/tmp and changes permissions so that # the portage group (which the user must be in) can read write # the unpacked files. Finally, it cds into that directory using # pushd. # # This requires that the user has sudo privileges for ebuild, # and in order to change the permissions in the work subdirectory to # allow the portage group to read/write, a sudo entry is needed as # well for "/bin/chmod g+rwx /home/username/tmp/portage". # (The user must be in the portage group to run ebuild.) # Lastly, the ebuild portage entry must grant permission for # the PORTAGE_TMPDIR, so that it isn't unpacked into /var/tmp/portage, # where it would be less safe to do recursive file ownership changes. # I use the following sudo entry to allow the wheel group to # specify PORTAGE_TMPDIR (and my user is in wheel): # # Defaults:%wheel env_keep=PORTAGE_TMPDIR # # TODO: port_dir (/usr/portage) and the subdir in PORTAGE_TMPDIR # (portage) shouldn't be hardcoded to their defaults as at present, # and ideally should get the values from the same place that portage # gets them. # # TODO: if no version is specified as an argument, then we currently # just use the ebuild file that was most recently modified, which # is probably less useful than getting the most recent version # by default. Fixing this is slightly involved because a dictionary # sort on the filename won't work, and neither a numeric nor # dictionary sort on the version will work either since there # are usually non-numbers in the version. view_package_source() { if [[ $# -lt 1 ]]; then err "Usage: view_package_source pkg-class/app-name [version]" return 1 fi if ! expr match "$1" "[-\.a-zA-Z0-9]*/[-\.a-zA-Z0-9]*" &> /dev/null; then err "\"$1\" is not a valid pkg-class/app-name." err "Usage: view_package_source pkg-class/app-name" return 1 fi unpack_dir=$HOME/tmp mkdir -p $unpack_dir port_dir=/usr/portage pkg_class_len=$(expr match $1 "[-\.A-Za-z0-9]*") pkg_class="${1:0:$pkg_class_len}" app_name="${1:$((${pkg_class_len} + 1))}" app_dir="$port_dir/$pkg_class/$app_name" if test -z "$2"; then freshest_ebuild="$(ls -t1 $app_dir/*.ebuild | head -n1)" ebuild_filename="$(echo $freshest_ebuild | egrep -o "[^/]*$")" else ebuild_filename="${app_name}-$2.ebuild" fi ebuild_path="$port_dir/$pkg_class/$app_name/$ebuild_filename" echo "Fetching $ebuild_path..." sudo PORTAGE_TMPDIR="$unpack_dir" ebuild $ebuild_path fetch echo "Unpacking $ebuild_path..." sudo PORTAGE_TMPDIR="$unpack_dir" ebuild $ebuild_path unpack # We can't choose a more specific path, because this exact path # needs to be permissible via sudo, so it has to be fixed. port_unpack_dir=$unpack_dir/portage echo "Changing ownership in $port_unpack_dir" sudo /bin/chmod -R g+rwx $port_unpack_dir cd_dir="$(\ls -td1 $port_unpack_dir/$pkg_class/$app_name-* | head -n1)/work" echo "Changing directory to $cd_dir" pushd $cd_dir return 0 } ## Source custom environment of user, where variables like # JAVA_HOME and other variables used below would be defined. test -f ~/.init.env.sh && source ~/.init.env.sh ## Set up encoding- and locale-related variables. overwrite=$(test "${VAR_OVERWRITE:-0}" "=" "1" && echo "1") # LANG and LC_ALL use USER_LANG. [ -z "$LANG" -o -n "$overwrite" ] && [ -n "$USER_LANG" ] \ && export LANG="${USER_LANG}" [ -z "$LC_ALL" -o -n "$overwrite" ] && [ -n "$USER_LANG" ] \ && export LC_ALL="${USER_LANG}" # Not sure what uses LANGUAGE, but I had it set from somewhere. [ -z "$LANGUAGE" -o -n "$overwrite" ] && [ -n "$USER_LANG" ] \ && export LANGUAGE="${USER_LANG}" # Some versions of gdm need GDM_LANG to be set for some purposes; I # needed it for xmonad (window manager running inside Gnome) to # use utf-8 as the default encoding. [ -z "$GDM_LANG" -o -n "$overwrite" ] && [ -n "$USER_LANG" ] \ && export GDM_LANG="${USER_LANG}" # LINGUAS uses USER_LINGUAS [ -z "$LINGUAS" -o -n "$overwrite" ] && [ -n "$USER_LINGUAS" ] \ && export LINGUAS="${USER_LINGUAS}" ## Add to path some common binary directories/install homes, if appropriate. # Add games binary dir if it exists and not root user. test "$(id -u)" != "0" && \ test -x /usr/games/bin && export PATH="/usr/games/bin:$PATH" # Add Java binaries if JAVA_HOME is set. test -n "$JAVA_HOME" && export PATH="$JAVA_HOME/bin:$PATH" # Add Scala binaries if SCALA_HOME is set. test -n "$SCALA_HOME" && export PATH="$SCALA_HOME/bin:$PATH" # Lastly, add user's custom binary directory, if present. These # binaries will be found before anything else on the PATH. test -x "$HOME/bin" && export PATH="$HOME/bin:$PATH" ## If user defined a CVS_PATH in .bashrc.custom.sh, set up a CVSROOT. if [ -n "$CVS_PATH" ]; then CVSROOT="$CVS_PATH" # If user also defined CVS_HOST, and this host isn't that host, # then convert the CVSROOT var to its remote equivalent (over SSH). if [ -n "$CVS_HOST" -a "$(hostname)" != "$CVS_HOST" ]; then CVSROOT=":ext:${CVS_USER-"${USER}"}@${CVS_HOST}${CVSROOT}" fi export CVSROOT fi # darcs and mercurial don't have the concept of 1 single root # directory on a server, so there's no setup for them here. ## The directory that keychain stores its environment files in, which can # be sourced to get info about the agent processes it started. This is # used by the keychain-related functions. K_DIR="$HOME/.keychain" ## K_HOST is the hostname that will be passed to keychain and which it uses # to create the environment files that may be sourced to get info about # the ssh-agent and gpg-agent processes that it starts. # We set it here so that it won't change later, using the user-defined # KEYCHAIN_HOST if it was given, and falling back to HOSTNAME and then # whatever `uname -n` outputs. if [ -n "$KEYCHAIN_HOST" ]; then # User manually set KEYCHAIN_HOST var in their custom file; use that. K_HOST="$KEYCHAIN_HOST" else # KEYCHAIN_HOST wasn't set; use HOSTNAME if available. if [ -n "$HOSTNAME" ]; then K_HOST="$HOSTNAME" else # Fallback to output of `uname -n`. K_HOST="$(uname -n)" fi fi ## Below are variables that support the keychain-related functions and # the keychain-related functions themselves. # # The functions are declared here rather than in functions.*.sh because # they need to have access to the user-defined variables that are # sourced after the function files have been sourced (in order to # allow the user to use those functions in their custom files). ## This is the base filename for keychain environment files, to which is # appended "-csh", "-csh-gpg", "-fish", "-fish-gpg", "-sh", "-sh-gpg" for # the various environment files. We only use the "-sh" and "-sh-gpg" files, # which are defined below, but this is defined for a base name for globbing. K_BASEFILE="$K_DIR/$K_HOST" ## The file that keychain creates for the ssh-agent environment. K_SSH_FILE="${K_DIR}/${K_HOST}-sh" ## The file that keychain creates for the gpg-agent environment. K_GPG_FILE="${K_DIR}/${K_HOST}-sh-gpg" ## Start keychain using an optional agent_timeout ($1), removing any old # keychain files first. This function assumes any existing keychain # environment files are stale and should be removed. The optional # first argument is an integer giving the number of minutes that # keychain should pass to ssh-agent for the default timeout of keys # that are added via ssh-add. It can be overridden later on a # case-by-case basis by calling ssh-add with the -t argument (which # takes the argument in seconds rather than minutes). start_keychain() { # Remove old keychain environment files, if any. if ls $K_BASEFILE-* &> /dev/null ; then debug "Removing old keychain environment files ..." rm -f $K_BASEFILE-* fi # If first argument is given, it is an integer representing the default # timeout that keychain should use for expiring keys. keychain_timeout=$(test -n "$1" && echo "--timeout $1") keychain_cmd="keychain -Q -q --noask --host '$K_HOST' $keychain_timeout" debug "Starting keychain with following command:\n$keychain_cmd" eval "$keychain_cmd" keychain_status=$? if [ $keychain_status -ne 0 ]; then err "Starting keychain failed with error code %d" $keychain_status return 1 fi # keychain started succesfully, so source the ssh and gpg files it created # in order to make the current environment aware of the agents started. # Sourcing the ssh env file reads the SSH_AGENT_PID and SSH_AUTH_SOCK # variables of the ssh-agent process, and sourcing the gpg env file reads # the GPG_AGENT_INFO variable of the gpg-agent process. [ -f "${K_SSH_FILE}" ] && . "${K_SSH_FILE}" [ -f "${K_GPG_FILE}" ] && . "${K_GPG_FILE}" return 0 } ## Sources any ssh-agent and gpg-agent environment files that have been # created by keychain, in order to make them visible to this process. source_keychain_env() { [ -f "${K_SSH_FILE}" ] && . "${K_SSH_FILE}" [ -f "${K_GPG_FILE}" ] && . "${K_GPG_FILE}" } ## Determine if ssh-agent is alive by checking for SSH_AGENT_PID and # verifying that the process can receive a signal. ssh_agent_alive() { test -n "$SSH_AGENT_PID" && kill -0 $SSH_AGENT_PID &> /dev/null } ## Determine if the ssh-agent authorization socket exists by checking # for SSH_AUTH_SOCK and verifying that the referenced socket exists. ssh_agent_socket_exists() { test -S "$SSH_AUTH_SOCK" } ## Determine if gpg-agent is alive by checking for GPG_AGENT_INFO # and verifying that the referenced file exists. gpg_agent_alive() { # The GPG_AGENT_INFO var will be something like the following path: # /tmp/gpg-9SRC5u/S.gpg-agent:11039:1 # The actual socket file is everything in the path before the first # colon, and the rest is presumably port info and other conf data # that gpg knows how to interpret. test -S "${GPG_AGENT_INFO%%:*}" } ## Determine the status of keychain. Return codes are as follows # 0) installed, started, valid, and visible in environment; # 1) installed, started, valid, not visible (env needs to be sourced); # 2) installed, started, stale (need to remove files and restart); # 3) installed, not started; # 4) not installed; keychain_status() { # Is keychain installed? if ! which keychain &> /dev/null ; then return 4 # Not installed fi # Has keychain been started? if ! ls ${K_BASEFILE}-* &> /dev/null ; then return 3 # Not started fi ## Are the environment files valid? # Check if ssh-agent is valid if [ -f "${K_SSH_FILE}" ]; then if ! (source "${K_SSH_FILE}" && ssh_agent_alive \ && ssh_agent_socket_exists); then debug "ssh-agent environment files not valid" return 2 fi fi # Check if gpg-agent is valid if [ -f "${K_GPG_FILE}" ]; then if ! (source "${K_GPG_FILE}" && gpg_agent_alive) ; then debug "gpg-agent environment files not valid" return 2 fi fi # Are the environment variables set in the current process? if [ -f "${K_SSH_FILE}" ]; then if [ -z "$SSH_AGENT_PID" -o -z "$SSH_AUTH_SOCK" ]; then return 1 fi fi if [ -f "${K_GPG_FILE}" -a -z "$GPG_AGENT_INFO" ]; then return 1 fi # If we get here, everything is perfect return 0 } ## Ensure that keychain is running and valid and that the necessary # environment variables used by ssh-agent and gpg-agent have been # set, attempting to fix anything that can be fixed and start or # restart keychain as necessary. # This function does nothing unless the USE_KEYCHAIN variable has # been set to a non-zero value. ensure_keychain_valid() { if [ -z "$USE_KEYCHAIN" -o "$USE_KEYCHAIN" = "0" ]; then return 0 fi # Determine the timeout to be used for keychain agent_timeout=$(passphrase_timeout) # A result of 0 from passphrase_timeout means not to start keychain # for this particular user@host combination. if [ "$agent_timeout" = "0" ]; then inf "passphrase_timeout returned 0, so not starting keychain." fi # Get current status of keychain, whether it's installed, runnning, etc. keychain_state=$(keychain_status; echo $?) debug "keychain_state=%d" $keychain_state # Depending on current state, clean up if necessary and determine whether # we need to start keychain. case $keychain_state in 4) # keychain not installed err "USE_KEYCHAIN varable was set, but keychain is not installed." to_start=0 ;; 3) # keychain installed, but not started to_start=1 ;; 2) # installed and started, but invalid or stale # remove old environment files and unset variables debug "Cleaning up stale keychain and unsetting agent vars." rm -f ${K_BASEFILE}-* [ -n "$SSH_AGENT_PID" ] && unset SSH_AGENT_PID [ -n "$SSH_AUTH_SOCK" ] && unset SSH_AUTH_SOCK [ -n "$GPG_AGENT_INFO" ] && unset GPG_AGENT_INFO to_start=1 ;; 1) # installed, started and valid, but not visible in this process debug "Reusing existing keychain environment in ${K_DIR}" [ -f "${K_SSH_FILE}" ] && source "${K_SSH_FILE}" [ -f "${K_GPG_FILE}" ] && source "${K_GPG_FILE}" to_start=0 ;; 0) # installed, running and visible; nothing else to do to_start=0 ;; *) # should never happen err "Unexpected status from keychain_status(): %d" $keychain_state to_start=0 ;; esac # If to_start was set to 1, then the environment has been cleaned and we # need to start keychain and source the resultant environment files. if [ $to_start -eq 1 ]; then start_keychain ${agent_timeout} && source_keychain_env return $? fi return 0 } # If SSH_KEY_APPS was given (e.g., "ssh scp darcs") and a SSH_KEYFILE # was defined and if this host is not in the exclude variable # SSH_ALIAS_EXLUDE_HOSTS, then create an alias for each app in SSH_KEY_APPS # that will ensure the key has been loaded (loading it if not) and # then invoke the app. make_ssh_aliases() { # First, make sure that the current host isn't one of the ones for which # we're not supposed to make aliases. if [ -n "$SSH_ALIAS_EXCLUDE_HOSTS" ]; then this_host="$(uname -n)" for excluded_host in $(tokenize " " "$SSH_ALIAS_EXCLUDE_HOSTS"); do if [ "$excluded_host" = "$this_host" ]; then return 1 fi done fi # Second, if the relevant vars were defined, create an alias for each app. if [ -n "$SSH_KEY_APPS" -a -n "$SSH_KEYFILE" ]; then for app in $(tokenize " " "$SSH_KEY_APPS"); do eval "alias $app=\"ensure_key $SSH_KEYFILE && $app\"" done fi } ## Finally, functions are defined, so we initiate the keychain setup, # which only works if USE_KEYCHAIN was set. ensure_keychain_valid ## And setup the ssh-related aliases that transparently ensure the key # was added; this only works if SSH_KEY_APPS and SSH_KEYFILE were set. make_ssh_aliases # Finally, source the user's custom init script, if present, where they can # add whatever they want and reuse any of these functions. [ -f "$HOME/.init.custom.sh" ] && source "$HOME/.init.custom.sh" # Set a variable to signal that this script has been sourced so that # other scripts, functions, etc. launched by this shell later can # determine that it doesn't need to be sourced again. export INIT_SYS_SOURCED="1"