476 lines
12 KiB
Bash
476 lines
12 KiB
Bash
#!/bin/bash
|
|
# Vim Note System, a simple script for managing encrypted plaintext notes
|
|
|
|
readonly VNS_STORE="${VNS_STORE:-$HOME/.vns}" # Where vns expects its note store
|
|
GPG_TTY="$(tty)" # Sets up gpg-agent for use
|
|
export GPG_TTY
|
|
|
|
vns_raise (){
|
|
|
|
# raise (message) (exit code)
|
|
# Prints error (message) to stderr, then exits with (exit code)
|
|
|
|
local -r VNS_RED_COLOR='\033[0;31m'
|
|
local -r VNS_RESET_COLOR='\033[0m'
|
|
|
|
printf "$VNS_RED_COLOR!$VNS_RESET_COLOR - %s\\n" "$1" 1>&2
|
|
exit "$2"
|
|
|
|
}
|
|
|
|
vns_report () {
|
|
|
|
local -r VNS_BLUE_COLOR='\033[0;34m'
|
|
local -r VNS_RESET_COLOR='\033[0m'
|
|
|
|
printf "${VNS_BLUE_COLOR}*${VNS_RESET_COLOR} - %s\n" "$*"
|
|
}
|
|
|
|
vns_checkDeps (){
|
|
|
|
# checkDeps
|
|
# Prints a list of missing dependencies
|
|
|
|
for DEP in 'vim' 'tree' 'git'; do
|
|
if test ! -e "$(command -v "$DEP" 2>/dev/null)"; then
|
|
printf "%s " "$DEP"
|
|
fi
|
|
done
|
|
|
|
}
|
|
|
|
vns_sanityCheck () {
|
|
|
|
# sanityCheck
|
|
# Terminates script if environment is not suitable for vns
|
|
|
|
# --- Dependencies ---
|
|
# Get list of missing dependencies
|
|
MISSING="$(vns_checkDeps)"
|
|
|
|
# Inform the user of missing dependencies and exit on code 20 if any were found
|
|
if [ -n "$MISSING" ]; then
|
|
vns_raise "Missing Dependencies: $MISSING" 20
|
|
fi
|
|
|
|
# --- Store ---
|
|
# Verify that the note store has been initialized
|
|
if [ ! -d "$VNS_STORE" ]; then
|
|
vns_raise "The vns store needs to be initialized (hint: -I)" 21
|
|
fi
|
|
|
|
}
|
|
|
|
vns_init () {
|
|
|
|
# init (GPG recipients...)
|
|
# Prepares $VNS_STORE for use
|
|
|
|
if [ "$#" -lt 1 ]; then
|
|
vns_raise "No GPG Recipients Specified" 3
|
|
|
|
elif [ -d "$VNS_STORE/.git" ]; then
|
|
|
|
# Reset list of GPG recipients
|
|
echo -n '' > "${VNS_STORE}/.gpg-id"
|
|
|
|
vns_reencrypt "$VNS_STORE" "$@"
|
|
else
|
|
# Create a new VNS Store
|
|
git init "$VNS_STORE" >/dev/null
|
|
vns_report "Initialized new VNS store"
|
|
|
|
fi
|
|
|
|
# Store GPG Recipients for later note creation
|
|
# whether re-encrypting, or initializing a new store
|
|
for recipient in "$@"; do
|
|
echo "$recipient" >> "${VNS_STORE}/.gpg-id"
|
|
done
|
|
vns_git add .gpg-id >/dev/null
|
|
vns_git commit -am "Updated GPG recipients" >/dev/null
|
|
}
|
|
|
|
vns_reencrypt () {
|
|
# reencrypt (GPG recipients...)
|
|
|
|
# Remove script name from arguments list
|
|
shift
|
|
|
|
# Construct list of arguments from provided recipients
|
|
local -a GPG_RECIPS;
|
|
for recipient in "$@"; do
|
|
GPG_RECIPS=( "${GPG_RECIPS[@]}" "-r" "$recipient" )
|
|
done
|
|
|
|
# Find all notes and re-encrypt them
|
|
find "${VNS_STORE}" -name "*.gpg" | while read -r FILE; do\
|
|
gpg --batch --decrypt "$FILE" 2>/dev/null | gpg --batch "${GPG_RECIPS[@]}" --encrypt --output "${FILE}_new".gpg >/dev/null 2>&1
|
|
mv "${FILE}_new.gpg" "$FILE";
|
|
done
|
|
|
|
vns_report "Re-encrypted existing notes with $*"
|
|
vns_git commit -am "Re-encrypted existing notes with ${GPG_RECIPS[*]}" >/dev/null 2>&1
|
|
}
|
|
|
|
vns_gpgid () {
|
|
|
|
# gpgid
|
|
# Echo all recipients listed in .gpg-id
|
|
|
|
# shellcheck disable=SC2002
|
|
cat "${VNS_STORE}/.gpg-id" | while read -r recipient; do
|
|
echo "-r"
|
|
echo "$recipient"
|
|
done
|
|
}
|
|
|
|
vns_printHelp (){
|
|
|
|
# printHelp
|
|
# Prints help information to stdout
|
|
|
|
printf "%s" "usage: vns [-cedlp] <notebook/section/name>"
|
|
printf "\\n%s" " vns -I GPG_RECIPIENT (GPG_RECIPIENTS...)"
|
|
printf "\\n%s" " vns -h"
|
|
printf "\\n%s" " vns git ..."
|
|
printf "\\n"
|
|
printf "\\n%s" " -c : Create note"
|
|
printf "\\n%s" " -d : Delete note(s)"
|
|
printf "\\n%s" " -e : Open note for editing"
|
|
printf "\\n%s" " -h : Display this message"
|
|
printf "\\n%s" " -i : import file as note"
|
|
printf "\\n%s" " -I : Initialize note store"
|
|
printf "\\n%s" " -l : List all notes in <notebook>"
|
|
printf "\\n%s" " -m : Merge two or more notes"
|
|
printf "\\n%s" " -p : Print note to console"
|
|
printf "\\n%s" " -r : Rename/move a note"
|
|
printf "\\n\\n"
|
|
|
|
}
|
|
|
|
vns_list () {
|
|
|
|
# list (notebook)
|
|
# Prints a tree containing all notes in the notebook
|
|
# If no notebook is specified, the entire store is used
|
|
|
|
#numNotes () { find "${VNS_STORE}" -name "*.gpg" \( ! -regex '.*/\..*' \) | wc -l; }
|
|
|
|
# Check for default behavior
|
|
if [ "$#" -lt 1 ]; then
|
|
local -r "NOTEBOOK"=""
|
|
printf "%s\\n" "vns"
|
|
else
|
|
readonly NOTEBOOK="$1"
|
|
printf "%s\\n" "$1"
|
|
fi
|
|
|
|
tree --noreport --prune "$VNS_STORE/$NOTEBOOK"\
|
|
| tail -n +2\
|
|
| sed s/\.gpg//g
|
|
|
|
}
|
|
|
|
vns_rm () {
|
|
|
|
# rm (note)
|
|
# removes (note) from the store
|
|
|
|
while [ "$#" -gt 0 ]; do
|
|
# Verify $1 is bound
|
|
if [ "$#" -lt 1 ]; then vns_raise "Insufficient arguments" 30; fi
|
|
|
|
# Verify the note exists
|
|
if [ ! -r "$VNS_STORE/$1.gpg" ]; then
|
|
vns_raise "Note does not exist" 31;
|
|
fi
|
|
|
|
# Delete the Note
|
|
vns_git rm "$VNS_STORE/$1.gpg" > /dev/null
|
|
vns_git commit -m "Deleted $1" > /dev/null
|
|
|
|
# Report success
|
|
vns_report "Deleted $1"
|
|
|
|
# Move to the next item
|
|
shift
|
|
done
|
|
|
|
}
|
|
|
|
vns_header () {
|
|
# header (title)
|
|
# Print note title and date in Markdown format
|
|
|
|
if [ "$#" -lt 1 ]; then
|
|
vns_raise "header: no note title provided" 4
|
|
else
|
|
echo "# $1"
|
|
printf "## %s" "$(date)"
|
|
fi
|
|
}
|
|
|
|
vns_create () {
|
|
|
|
# create (note)
|
|
# Create a new note and open it for editing
|
|
|
|
local -r VNS_RED_COLOR='\033[0;31m'
|
|
local -r VNS_RESET_COLOR='\033[0m'
|
|
|
|
if [ "$#" -lt 1 ]; then
|
|
printf "usage: %s %s\n" "$(basename "$0")" "-c [notebook/section/](name)"
|
|
return
|
|
fi
|
|
|
|
# Refuse to overwrite a note
|
|
if [ -r "$VNS_STORE/$1.gpg" ]; then vns_raise "Note already exists" 41; fi
|
|
|
|
# If the note belongs to a new notebook, create the notebook
|
|
if [ ! -d "$(dirname "$1")" ]; then mkdir -p "$VNS_STORE/$(dirname "$1")"; fi
|
|
|
|
# Create empty note
|
|
mkdir -p "$(dirname "$VNS_STORE/$1.gpg")"
|
|
vns_header "$(basename "$1")" | gpg --batch "$(vns_gpgid)" --encrypt -o "$VNS_STORE/$1.gpg" 2>/dev/null
|
|
|
|
# Edit the note
|
|
vim "$VNS_STORE/$1.gpg"
|
|
|
|
# Update vns_git
|
|
vns_git add "$VNS_STORE/$1.gpg" > /dev/null
|
|
vns_git commit -m "Added $1" > /dev/null
|
|
|
|
}
|
|
|
|
vns_edit () {
|
|
|
|
# edit (note)
|
|
# Open (note) for editing in Vim
|
|
|
|
# Verify $1 is bound
|
|
if [ "$#" -lt 1 ]; then vns_raise "Insufficient arguments" 50; fi
|
|
|
|
# Refuse to edit non-existent notes
|
|
if [ ! -r "$VNS_STORE/$1.gpg" ]; then vns_raise "Note not found: $1" 51; fi
|
|
|
|
# Edit the specified note
|
|
vim "$VNS_STORE/$1.gpg"
|
|
|
|
# Update vns_git
|
|
vns_git add "$VNS_STORE/$1.gpg" > /dev/null
|
|
vns_git commit -m "Edited $1" > /dev/null
|
|
}
|
|
|
|
vns_import () {
|
|
|
|
# import (src) (dest)
|
|
# If src is the path to a file, and dest is a valid note name,
|
|
# import src dest copies an encrypted version of src to the note store
|
|
|
|
if [ "$#" -lt 2 ]; then
|
|
printf "usage: %s %s\n" "$(basename "$0")" "-i (source) (destination)"
|
|
return
|
|
fi
|
|
|
|
if [ ! -f "$1" ]; then
|
|
vns_raise "Cannot read $1" 92
|
|
|
|
elif [ -f "$VNS_STORE/$2" ]; then
|
|
printf "%s" "Note already exists. Overwrite? (Y/N) "
|
|
local response=''; read -r response
|
|
case "$response" in
|
|
Y|y)
|
|
rm "$VNS_STORE/$2";;
|
|
*)
|
|
vns_raise "User declined overwrite" 0;;
|
|
esac
|
|
fi
|
|
|
|
printf "%s:" "Specify GPG Public Key: "
|
|
local gpg_key=''; read -r gpg_key
|
|
|
|
gpg2 --batch -r "$gpg_key" -o "$VNS_STORE/$2.gpg" -e "$1"
|
|
|
|
vns_git add "$VNS_STORE/$2.gpg"
|
|
vns_git commit -m "Imported file $1 as $2"
|
|
|
|
}
|
|
|
|
vns_print () {
|
|
|
|
# print (note)
|
|
# print the given note to stdout
|
|
|
|
# Verify $1 is bound
|
|
if [ "$#" -lt 1 ]; then
|
|
vns_raise "Insufficient arguments" 11
|
|
|
|
# Verify $1 exists
|
|
elif ! [ -r "$VNS_STORE/$1.gpg" ]; then
|
|
vns_raise "Could not read $1" 12
|
|
|
|
else
|
|
# Decrypt the specified note
|
|
gpg2 --batch -d "$VNS_STORE/$1.gpg"
|
|
fi
|
|
}
|
|
|
|
vns_mv () {
|
|
|
|
# mv (src) (dest)
|
|
# If (src) is a note in the vns store, mv src dest moves src to dest
|
|
|
|
# Ensure two arguments were provided
|
|
if [ "$#" -lt 2 ]; then vns_raise "Insufficient arguments" 70; fi
|
|
|
|
# Ensure source can be read
|
|
if [ ! -r "$VNS_STORE/$1.gpg" ]; then vns_raise "Cannot read $1" 71; fi
|
|
|
|
# Refuse to overwrite existing notes
|
|
if [ -r "$VNS_STORE/$2.gpg" ]; then vns_raise "Note $2 already exists" 72; fi
|
|
|
|
vns_git mv "$VNS_STORE/$1.gpg" "$VNS_STORE/$2.gpg"
|
|
|
|
}
|
|
|
|
vns_merge () {
|
|
|
|
# merge (output) (notes[])
|
|
# If notes[] is an array of note names and output is a new note name,
|
|
# merge (output) (notes[]) concatenates notes[] into output.
|
|
if [ "$#" -lt 2 ]; then
|
|
printf "usage: %s %s\n" "$(basename "$0")" "-m (output) (notes...)"
|
|
return
|
|
fi
|
|
|
|
if [ -r "$VNS_STORE/$1.gpg" ]; then vns_raise "Output file already exists." 82; fi
|
|
if [ ! -r "$VNS_STORE/$2.gpg" ]; then vns_raise "Could not read $2" 83; fi
|
|
|
|
local -r NOTE_NAME="$1"
|
|
local -r OUTPUT="${VNS_STORE}/${1}.gpg"
|
|
shift
|
|
|
|
vns_gpgcat () {
|
|
for note in "$@"; do
|
|
if [ ! -r "$VNS_STORE/$note.gpg" ]; then
|
|
rm "$VNS_STORE/$1.gpg"
|
|
vns_raise "Could not read $note" 83;
|
|
fi
|
|
|
|
printf "File: %s\n" "$note"
|
|
gpg2 --batch -d "$VNS_STORE/$note.gpg" 2>/dev/null
|
|
printf "\n"
|
|
done
|
|
}
|
|
|
|
vns_gpgcat "$@" | gpg2 --batch --yes "$(vns_gpgid)" --encrypt -o "$OUTPUT"
|
|
|
|
printf "%s" "Remove old notes? (y/N) "
|
|
local response=''; read -r response
|
|
case "$response" in
|
|
Y|y)
|
|
for note in "$@"; do
|
|
vns_git rm "$VNS_STORE/$note.gpg"
|
|
done;;
|
|
esac
|
|
vns_git add "$OUTPUT" >/dev/null 2>&1
|
|
vns_git commit -m "merged files $* into $NOTE_NAME" >/dev/null 2>&1
|
|
vns_report "Successfully merged files $* into $NOTE_NAME"
|
|
}
|
|
|
|
vns_duplicate () {
|
|
|
|
if [ "$#" -lt 2 ]; then
|
|
vns_report "usage: $(basename "$0") -u (source) (destination)"
|
|
return
|
|
fi
|
|
|
|
if [ ! -f "$VNS_STORE/$1.gpg" ]; then
|
|
vns_raise "Cannot read $1" 102
|
|
|
|
elif [ -f "$VNS_STORE/$2.gpg" ]; then
|
|
printf "%s" "Note already exists. Overwrite? (Y/N) "
|
|
response=''; read -r response
|
|
case "$response" in
|
|
Y|y)
|
|
rm "$VNS_STORE/$2.gpg";;
|
|
*)
|
|
vns_raise "User declined overwrite" 0;;
|
|
esac
|
|
fi
|
|
|
|
cp "$VNS_STORE/$1.gpg" "$VNS_STORE/$2.gpg"
|
|
vns_git add "$VNS_STORE/$2.gpg"
|
|
}
|
|
|
|
vns_git () {
|
|
# Simple passthrough for executing git commands in the VNS store
|
|
git -C "$VNS_STORE" "$@"
|
|
}
|
|
|
|
vns () {
|
|
|
|
# Bypass sanity check if told to initialize store
|
|
if [ "$1" != "-I" ]; then
|
|
vns_sanityCheck;
|
|
fi
|
|
|
|
case "$1" in
|
|
"-c")
|
|
shift
|
|
vns_create "$@"
|
|
;;
|
|
"-d")
|
|
shift
|
|
vns_rm "$@"
|
|
;;
|
|
"-e")
|
|
shift
|
|
vns_edit "$@"
|
|
;;
|
|
"-l")
|
|
shift
|
|
vns_list "$@"
|
|
;;
|
|
"-h")
|
|
shift
|
|
vns_printHelp "$@"
|
|
;;
|
|
"-I")
|
|
shift
|
|
vns_init "$@"
|
|
;;
|
|
"-i")
|
|
shift
|
|
vns_import "$@"
|
|
;;
|
|
"-m")
|
|
shift
|
|
vns_merge "$@"
|
|
;;
|
|
"-p")
|
|
shift
|
|
vns_print "$@"
|
|
;;
|
|
"-r")
|
|
shift
|
|
vns_mv "$@"
|
|
;;
|
|
"-u")
|
|
shift
|
|
vns_duplicate "$@"
|
|
;;
|
|
"git")
|
|
shift
|
|
vns_git "$@"
|
|
;;
|
|
*)
|
|
vns_list "$@"
|
|
esac
|
|
|
|
exit 0;
|
|
}
|
|
|
|
vns "$@"
|