Files
blackbox/bin/_blackbox_common.sh

569 lines
12 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
2014-07-07 20:30:16 -04:00
#
# Common constants and functions used by the blackbox_* utilities.
#
# Usage:
2014-09-08 20:25:38 +00:00
#
# set -e
# . _blackbox_common.sh
2014-07-07 20:30:16 -04:00
# Where in the VCS repo should the blackbox data be found?
2015-02-27 01:01:48 +07:00
: "${BLACKBOXDATA:=keyrings/live}" ; # If BLACKBOXDATA not set, set it.
# If $EDITOR is not set, set it to "vi":
2015-02-27 01:01:48 +07:00
: "${EDITOR:=vi}" ;
# Outputs a string that is the base directory of this VCS repo.
# By side-effect, sets the variable VCS_TYPE to either 'git', 'hg',
2014-10-15 11:01:52 -07:00
# 'svn' or 'unknown'.
function _determine_vcs_base_and_type() {
if git rev-parse --show-toplevel 2>/dev/null ; then
VCS_TYPE=git
2014-10-15 11:01:52 -07:00
elif [ -d ".svn" ] ; then
2014-10-18 23:30:38 -07:00
#find topmost dir with .svn sub-dir
parent=""
grandparent="."
mydir="$(pwd)"
2014-10-18 23:30:38 -07:00
while [ -d "$grandparent/.svn" ]; do
parent=$grandparent
grandparent="$parent/.."
done
if [ ! -z "$parent" ]; then
2015-02-27 01:01:48 +07:00
cd "$parent"
pwd
2014-10-18 23:30:38 -07:00
else
exit 1
fi
2015-02-27 01:01:48 +07:00
cd "$mydir"
2014-10-15 11:01:52 -07:00
VCS_TYPE=svn
elif hg root 2>/dev/null ; then
# NOTE: hg has to be tested last because it always "succeeds".
VCS_TYPE=hg
else
# We aren't in a repo at all. Assume the cwd is the root
# of the tree.
echo .
VCS_TYPE=unknown
fi
export VCS_TYPE
# FIXME: Verify this function by checking for .hg or .git
# after determining what we believe to be the answer.
2014-07-07 20:30:16 -04:00
}
REPOBASE=$(_determine_vcs_base_and_type)
KEYRINGDIR="$REPOBASE/$BLACKBOXDATA"
BB_ADMINS_FILE="blackbox-admins.txt"
BB_ADMINS="${KEYRINGDIR}/${BB_ADMINS_FILE}"
BB_FILES_FILE="blackbox-files.txt"
BB_FILES="${KEYRINGDIR}/${BB_FILES_FILE}"
SECRING="${KEYRINGDIR}/secring.gpg"
2015-02-27 01:01:48 +07:00
: "${DECRYPT_UMASK:=0022}" ;
2015-02-11 13:47:00 -06:00
# : ${DECRYPT_UMASK:=o=} ;
# Return error if not on cryptlist.
function is_on_cryptlist() {
# Assumes $1 does NOT have the .gpg extension
local rname=$(vcs_relative_path "$1")
grep -F -x -s -q "$rname" "$BB_FILES"
}
2014-07-07 20:30:16 -04:00
# Exit with error if a file exists.
function fail_if_exists() {
if [[ -f "$1" ]]; then
echo ERROR: "$1" exists. "$2" >&2
echo Exiting... >&2
2014-07-07 20:30:16 -04:00
exit 1
fi
}
# Exit with error if a file is missing.
function fail_if_not_exists() {
if [[ ! -f "$1" ]]; then
echo ERROR: "$1" not found. "$2" >&2
echo Exiting... >&2
2014-07-07 20:30:16 -04:00
exit 1
fi
}
# Exit we we aren't in a VCS repo.
function fail_if_not_in_repo() {
_determine_vcs_base_and_type
if [[ $VCS_TYPE = "unknown" ]]; then
echo "ERROR: This must be run in a VCS repo: git, hg, or svn." >&2
echo Exiting... >&2
exit 1
fi
}
2014-07-07 20:30:16 -04:00
# Exit with error if filename is not registered on blackbox list.
function fail_if_not_on_cryptlist() {
# Assumes $1 does NOT have the .gpg extension
local name="$1"
if ! is_on_cryptlist "$name" ; then
echo "ERROR: $name not found in $BB_FILES" >&2
echo "PWD="$(/bin/pwd) >&2
echo 'Exiting...' >&2
2014-07-07 20:30:16 -04:00
exit 1
fi
}
# Exit with error if keychain contains secret keys.
function fail_if_keychain_has_secrets() {
if [[ -s ${SECRING} ]]; then
echo 'ERROR: The file' "$SECRING" 'should be empty.' >&2
echo 'Did someone accidentally add this private key to the ring?' >&2
echo 'Exiting...' >&2
2014-07-07 20:30:16 -04:00
exit 1
fi
}
function get_pubring_path() {
if [[ -f "${KEYRINGDIR}/pubring.gpg" ]]; then
echo "${KEYRINGDIR}/pubring.gpg"
else
echo "${KEYRINGDIR}/pubring.kbx"
fi
}
2014-07-07 20:30:16 -04:00
# Output the unencrypted filename.
function get_unencrypted_filename() {
echo $(dirname "$1")/$(basename "$1" .gpg) | sed -e 's#^\./##'
}
# Output the encrypted filename.
function get_encrypted_filename() {
echo $(dirname "$1")/$(basename "$1" .gpg).gpg | sed -e 's#^\./##'
}
# Prepare keychain for use.
function prepare_keychain() {
echo '========== Importing keychain: START' >&2
gpg --import "$(get_pubring_path)" 2>&1 | egrep -v 'not changed$' >&2
echo '========== Importing keychain: DONE' >&2
2014-07-07 20:30:16 -04:00
}
# Add file to list of encrypted files.
function add_filename_to_cryptlist() {
# If the name is already on the list, this is a no-op.
# However no matter what the datestamp is updated.
local name=$(vcs_relative_path "$1")
2014-07-07 20:30:16 -04:00
if grep -s -q "$name" "$BB_FILES" ; then
echo ========== File is registered. No need to add to list.
else
echo ========== Adding file to list.
touch "$BB_FILES"
sort -u -o "$BB_FILES" <(echo "$name") "$BB_FILES"
fi
}
# Removes a file from the list of encrypted files
function remove_filename_from_cryptlist() {
# If the name is not already on the list, this is a no-op.
local name=$(vcs_relative_path "$1")
if ! grep -s -q "$name" "$BB_FILES" ; then
echo ========== File is not registered. No need to remove from list.
else
echo ========== Removing file from list.
remove_line "$BB_FILES" "$name"
fi
}
2014-07-07 20:30:16 -04:00
# Print out who the current BB ADMINS are:
function disclose_admins() {
echo ========== blackbox administrators are:
cat "$BB_ADMINS"
}
# Encrypt file, overwriting .gpg if it exists.
function encrypt_file() {
local unencrypted
local encrypted
unencrypted="$1"
encrypted="$2"
echo "========== Encrypting: $unencrypted" >&2
gpg --use-agent --no-tty --yes --trust-model=always --encrypt -o "$encrypted" $(awk '{ print "-r" $1 }' < "$BB_ADMINS") "$unencrypted" >&2
echo '========== Encrypting: DONE' >&2
2014-07-07 20:30:16 -04:00
}
# Decrypt .gpg file, asking "yes/no" before overwriting unencrypted file.
function decrypt_file() {
local encrypted
local unencrypted
local old_umask
2014-07-07 20:30:16 -04:00
encrypted="$1"
unencrypted="$2"
echo '========== EXTRACTING ''"'$unencrypted'"' >&2
old_umask=$(umask)
2015-02-27 01:01:48 +07:00
umask "$DECRYPT_UMASK"
gpg --use-agent --no-tty -q --decrypt -o "$unencrypted" "$encrypted" >&2
2015-02-27 01:01:48 +07:00
umask "$old_umask"
2014-07-07 20:30:16 -04:00
}
# Decrypt .gpg file, overwriting unencrypted file if it exists.
function decrypt_file_overwrite() {
local encrypted
local unencrypted
local old_hash
local new_hash
local old_umask
2014-07-07 20:30:16 -04:00
encrypted="$1"
unencrypted="$2"
if [[ -f "$unencrypted" ]]; then
2014-09-01 18:59:22 +00:00
old_hash=$(md5sum_file "$unencrypted")
2014-07-07 20:30:16 -04:00
else
old_hash=unmatchable
fi
old_umask=$(umask)
2015-02-27 01:01:48 +07:00
umask "$DECRYPT_UMASK"
gpg --use-agent --no-tty --yes -q --decrypt -o "$unencrypted" "$encrypted" >&2
2015-02-27 01:01:48 +07:00
umask "$old_umask"
2014-09-01 18:59:22 +00:00
new_hash=$(md5sum_file "$unencrypted")
2015-02-27 01:01:48 +07:00
if [[ "$old_hash" != "$new_hash" ]]; then
echo "========== EXTRACTED $unencrypted" >&2
2014-07-07 20:30:16 -04:00
fi
}
# Shred a file. If shred binary does not exist, delete it.
function shred_file() {
local name
local CMD
local OPT
name="$1"
if which shred >/dev/null ; then
CMD=shred
OPT=-u
elif which srm >/dev/null ; then
#NOTE: srm by default uses 35-pass Gutmann algorithm
CMD=srm
OPT=-f
2014-07-07 20:30:16 -04:00
else
CMD=rm
OPT=-f
fi
$CMD $OPT -- "$name"
2014-07-07 20:30:16 -04:00
}
# $1 is the name of a file that contains a list of files.
# For each filename, output the individual subdirectories
# leading up to that file. i.e. one one/two one/two/three
function enumerate_subdirs() {
local listfile
local dir
local filename
listfile="$1"
while read filename; do
dir=$(dirname "$filename")
while [[ $dir != '.' && $dir != '/' ]]; do
2015-02-27 01:01:48 +07:00
echo "$dir"
dir=$(dirname "$dir")
2014-07-07 20:30:16 -04:00
done
done <"$listfile" | sort -u
}
# chdir to the base of the repo.
function change_to_vcs_root() {
if [[ $REPOBASE = '' ]]; then
echo 'ERROR: _determine_vcs_base_and_type failed to set REPOBASE.'
exit 1
fi
cd "$REPOBASE"
}
2014-10-13 17:26:41 +00:00
# Output the path of a file relative to the repo base
function vcs_relative_path() {
# Usage: vcs_relative_path file
local name="$1"
python -c 'import os ; print(os.path.relpath("'"$(pwd -P)"'/'"$name"'", "'"$REPOBASE"'"))'
2014-10-13 17:26:41 +00:00
}
# Removes a line from a text file
function remove_line() {
local tempfile
tempfile="${1}.blackbox-temp.${RANDOM}.$$"
# Ensure source file exists
touch "$1"
grep -Fsxv "$2" "$1" > "$tempfile" || true
# Using cat+rm instead of cp will preserve permissions/ownership
cat "$tempfile" > "$1"
rm "$tempfile"
}
2014-10-13 17:26:41 +00:00
#
# Portability Section:
#
#
# Abstract the difference between Linux and Mac OS X:
#
function md5sum_file() {
# Portably generate the MD5 hash of file $1.
case $(uname -s) in
Darwin )
md5 -r "$1" | awk '{ print $1 }'
;;
Linux )
md5sum "$1" | awk '{ print $1 }'
;;
2014-10-31 15:15:33 -07:00
CYGWIN* )
md5sum "$1" | awk '{ print $1 }'
;;
2014-10-13 17:26:41 +00:00
* )
echo 'ERROR: Unknown OS. Exiting.'
exit 1
;;
esac
}
#
# Abstract the difference between git and hg:
#
# Are we in git, hg, or unknown repo?
2014-07-07 20:30:16 -04:00
function which_vcs() {
if [[ $VCS_TYPE = '' ]]; then
_determine_vcs_base_and_type >/dev/null
fi
echo "$VCS_TYPE"
2014-07-07 20:30:16 -04:00
}
2014-07-07 20:30:16 -04:00
# Is this file in the current repo?
function is_in_vcs() {
2015-06-12 13:27:42 -05:00
is_in_$(which_vcs) "$@"
2014-07-07 20:30:16 -04:00
}
# Mercurial
2014-07-07 20:30:16 -04:00
function is_in_hg() {
local filename
filename="$1"
if hg locate "$filename" ; then
echo true
else
echo false
fi
}
# Git:
2014-07-07 20:30:16 -04:00
function is_in_git() {
local filename
filename="$1"
if git ls-files --error-unmatch >/dev/null 2>&1 -- "$filename" ; then
echo true
else
echo false
fi
}
2014-10-15 11:01:52 -07:00
# Subversion
function is_in_svn() {
local filename
filename="$1"
if svn list "$filename" ; then
echo true
else
echo false
2015-02-27 01:01:48 +07:00
fi
2014-10-15 11:01:52 -07:00
}
2015-03-23 15:07:26 -04:00
# Perforce
function is_in_p4() {
local filename
filename="$1"
if p4 list "$filename" ; then
echo true
else
echo false
fi
}
# No repo
function is_in_unknown() {
echo true
}
# Add a file to the repo (but don't commit it).
function vcs_add() {
2015-06-12 13:27:42 -05:00
vcs_add_$(which_vcs) "$@"
}
# Mercurial
function vcs_add_hg() {
2015-06-12 13:27:42 -05:00
hg add "$@"
}
# Git
function vcs_add_git() {
2015-06-12 13:27:42 -05:00
git add "$@"
}
2014-10-15 11:01:52 -07:00
# Subversion
function vcs_add_svn() {
2015-06-12 13:27:42 -05:00
svn add --parents "$@"
2014-10-15 11:01:52 -07:00
}
2015-03-23 15:07:26 -04:00
# Perfoce
function vcs_add_p4() {
2015-06-12 13:27:42 -05:00
p4 add "$@"
2015-03-23 15:07:26 -04:00
}
# No repo
function vcs_add_unknown() {
:
}
2014-10-13 17:26:41 +00:00
# Commit a file to the repo
function vcs_commit() {
2015-06-12 13:27:42 -05:00
vcs_commit_$(which_vcs) "$@"
}
# Mercurial
function vcs_commit_hg() {
2015-06-12 13:27:42 -05:00
hg commit -m"$@"
}
# Git
function vcs_commit_git() {
2015-06-12 13:27:42 -05:00
git commit -m"$@"
}
2014-10-15 11:01:52 -07:00
# Subversion
function vcs_commit_svn() {
2015-06-12 13:27:42 -05:00
svn commit -m"$@"
2014-10-15 11:01:52 -07:00
}
2015-03-23 15:07:26 -04:00
# Perforce
function vcs_commit_p4() {
2015-06-12 13:27:42 -05:00
p4 submit -d "$@"
2015-03-23 15:07:26 -04:00
}
# No repo
function vcs_commit_unknown() {
:
}
2014-10-15 11:01:52 -07:00
# Remove file from repo, even if it was deleted locally already.
# If it doesn't exist yet in the repo, it should be a no-op.
function vcs_remove() {
2015-06-12 13:27:42 -05:00
vcs_remove_$(which_vcs) "$@"
}
# Mercurial
function vcs_remove_hg() {
2015-06-12 13:27:42 -05:00
hg rm -A -- "$@"
}
# Git
function vcs_remove_git() {
2015-06-12 13:27:42 -05:00
git rm --ignore-unmatch -f -- "$@"
}
2014-10-15 11:01:52 -07:00
# Subversion
function vcs_remove_svn() {
2015-06-12 13:27:42 -05:00
svn delete "$@"
2014-10-15 11:01:52 -07:00
}
2015-03-23 15:07:26 -04:00
# Perforce
function vcs_remove_p4() {
2015-06-12 13:27:42 -05:00
p4 delete "$@"
2015-03-23 15:07:26 -04:00
}
# No repo
function vcs_remove_unknown() {
:
}
# Ignore a file in a repo. If it was already ignored, this is a no-op.
function vcs_ignore() {
local file
for file in "$@"; do
vcs_ignore_$(which_vcs) "$file"
done
}
# Mercurial
function vcs_ignore_hg() {
vcs_ignore_generic_file "$REPOBASE/.hgignore" "$file"
}
# Git
function vcs_ignore_git() {
vcs_ignore_generic_file "$REPOBASE/.gitignore" "$file"
}
# Subversion
function vcs_ignore_svn() {
svn propset svn:ignore "$(vcs_relative_path "$file")"
}
# Perforce
function vcs_ignore_p4() {
:
}
# No repo
function vcs_ignore_unknown() {
:
}
# Generic - add line to file
function vcs_ignore_generic_file() {
local file
file="$(vcs_relative_path "$2")"
file="${file/\$\//}"
file="$(echo "/$file" | sed 's/\([\*\?]\)/\\\1/g')"
if ! grep -Fsx "$file" "$1" > /dev/null; then
echo "$file" >> "$1"
vcs_add "$1"
fi
}
# Notice (un-ignore) a file in a repo. If it was not ignored, this is
# a no-op
function vcs_notice() {
local file
for file in "$@"; do
vcs_notice_$(which_vcs) "$file"
done
}
# Mercurial
function vcs_notice_hg() {
vcs_notice_generic_file "$REPOBASE/.hgignore" "$file"
}
# Git
function vcs_notice_git() {
vcs_notice_generic_file "$REPOBASE/.gitignore" "$file"
}
# Subversion
function vcs_notice_svn() {
svn propdel svn:ignore "$(vcs_relative_path "$file")"
}
# Perforce
function vcs_notice_p4() {
:
}
# No repo
function vcs_notice_unknown() {
:
}
# Generic - remove line to file
function vcs_notice_generic_file() {
local file
file="$(vcs_relative_path "$2")"
file="${file/\$\//}"
file="$(echo "/$file" | sed 's/\([\*\?]\)/\\\1/g')"
if grep -Fsx "$file" "$1" > /dev/null; then
remove_line "$1" "$file"
vcs_add "$1"
fi
if grep -Fsx "${file:1}" "$1" > /dev/null; then
echo "WARNING: Found a non-absolute ignore match in $1"
echo "WARNING: Confirm the pattern is intended to only exclude $file"
echo "WARNING: If so, manually update the ignore file"
fi
}