# 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"