From e4e4718128cbd11a29b73381137e7b24021fe326 Mon Sep 17 00:00:00 2001 From: Jon-William Lewis Date: Thu, 24 Aug 2017 16:10:42 -0500 Subject: [PATCH] SNS suceeded by VNS --- README.md | 60 +++++-------- sns.sh | 256 ------------------------------------------------------ vns.sh | 218 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 242 insertions(+), 292 deletions(-) delete mode 100755 sns.sh create mode 100755 vns.sh diff --git a/README.md b/README.md index e8ff71d..076f862 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,25 @@ -Simple Note System +Vim Note System ================== ## About -The Simple Note System is a shell script partially inspired by [pass], in that +The Vim Note System is a shell script partially inspired by [pass], in that it stores notes as normal, plaintext, files in normal folders. It uses the -environment-specified editor, and can be configured to use GPG encryption. +vim-gnupg plugin, and asymmetric GPG encryption. -SNS was originally conceived one morning during an update to a popular note-taking -app. The thought occurred that a note system need not reinvent the wheel with -its own GUI editor and proprietary file format, but instead could use the tools -already provided by the operating system. +VNS, originally SNS, was originally conceived one morning during an update +to a popular note-taking app. The thought occurred that a note system need +not reinvent the wheel with its own GUI editor and proprietary file format, +but instead could use the tools already provided by the operating system. As it developed, OpenSSL encryption was dropped in favor of GPG, and the script -was almost entirely rewritten as SNSv2, and rewritten again as SNS v2a. +was almost entirely rewritten as SNSv2, rewritten again as SNS v2a, and forked +as VNS 1.0. - simple note system + vim note system ================== - usage: sns [-cedlp] - sns [-hi] + usage: vns [-cedlp] + vns [-hi] -c | --create : Create note -d | --delete : Delete note -e | --edit : Open note for editing @@ -28,51 +29,38 @@ was almost entirely rewritten as SNSv2, and rewritten again as SNS v2a. -p | --print : Print note to console ## Installing - ### Notice: -If you are upgrading to SNS v2a from an earlier version, please move -$HOME/.config/sns to $HOME/.local/sns +To migrate from SNS, simply move +$HOME/.local/sns to $HOME/.local/vns, +then append ".gpg" to each note name, for example +`find "$HOME/.local/vns" -type f -exec mv {} {}.gpg \;` -To install, place `sns.sh` in your path, +To install, place `vns` in your path, -To uninstall, remove the files you copied. Notes will still exist in $HOME/.local/sns +To uninstall, remove the files you copied. Notes will still exist in $HOME/.local/vns -Once installed, SNS will require you to run `sns -i [gpgkey]`, to indicate you would like +Once installed, VNS will require you to run `sns -i [gpgkey]`, to indicate you would like to create its note store and write its default configuration. By default, SNS will set itself up in `~/.local/sns` **A word about encryption:** -When editing an encrypted note, SNS will decrypt -the note to a temporary file inside its store. The temporary file will have a -random name, but a predictable location. It can only be read by the user and -root, and is deleted after editing, but can be recovered by forensic utilities. -SNS's encryption is mainly useful when the store is being _transferred over a -network_. - -## Vim Encryption -As of SNS v2b, SNS will look for a file named .vim in its store. If found, SNS will start in Vim mode, calling Vim directly instead of the environment's $EDITOR, and using Vim's native functionality for in-place encryption. - -At this time, SNS does not provide a way to convert between store formats. Additionally, SNS v2b does not support printing Vim-encrypted notes to stdout; you will need to open the note for editing and copy/paste out of Vim for the time being. - -Finally, when using Vim mode, treat -c and -e separately. +Encryption is handled by the vim-gnupg plugin. By default, the plugin creates temporary files on disk to +edit GPG-encrypted files. Setting `let g:GPGUsePipes=1` in .vimrc will bypass the creation of these files, +but the plugin author warns doing so may break certain CLI-based GPG agents. ## Tips and Tricks * To list all notes in all notebooks, use `sns -l .` ## Credits -The code here is my own, however much of SNS v2a's design and behavior was +The code here is my own, however much of VNS's design and behavior was influenced by [pass]. ## License -Simple Note System is licensed under the terms of the GNU General Public License +Vim Note System is licensed under the terms of the GNU General Public License Version 2, as detailed in `LICENSE`. -## To Do -* Switch back to calling $EDITOR instead of vim -* implement in-place encryption (vim only) - ## Bugs and Feature Requests If something seems off, or just doesn't work, please open an issue and I'll look into it. diff --git a/sns.sh b/sns.sh deleted file mode 100755 index f0eaaa2..0000000 --- a/sns.sh +++ /dev/null @@ -1,256 +0,0 @@ -#!/bin/bash -XL_PRODUCT="Simple Note System" -XL_VER="v2b" - -# Environment Constants -readonly SNS_STORE="$HOME/.local/sns" -readonly SNS_KEYFILE="$SNS_STORE/.pubkey" -readonly IFS=$'\n\t' -set -euo pipefail - -readonly SNS_RED_COLOR='\033[1;31m' -readonly SNS_YELLOW_COLOR='\033[1;33m' -readonly SNS_RESET_COLOR='\033[0m' - -# Erroneous Exit Constants - - # Invocation Errors - readonly SNS_ERR_NO_STORE=5 # The sns store needs to be initialized - readonly SNS_ERR_NO_OPTS=10 # No mode argument was specified - readonly SNS_ERR_NS_ARGS=11 # The specified mode requires an argument - readonly SNS_ERR_NO_EDTR=15 # No editor specified in environment - # Dependency Errors - readonly SNS_ERR_DEPS=20 # Base error - add the following codes - readonly SNS_ERR_NO_gpg2=1 # GPG is not installed - readonly SNS_ERR_NO_tree=2 # Tree is not installed - readonly SNS_ERR_NO_git=3 # Git is not installed - -# Global Variables -typeset -i SNS_SID="$RANDOM" -typeset -a SNS_ACTION=("") -typeset -a MISSING_DEPS -typeset -i SNS_EXIT=0 -declare SNS_PUBKEY= -# Function Definitions -# sns_printError Prints an error message -# sns_NoteHeader Prints standard note header to stdout -# sns_checkDeps Checks the system for required dependencies -# sns_checkStore Checks if $SNS_STORE exists -# sns_sanityCheck Wrapper for sns_checkDeps and sns_checkStore -# ---- -# sns_initStore Initializes the SNS Store ( -i) -# sns_printHelp Prints help page ( -h) -# sns_list Lists all notes in `tree` format ( *) -# sns_create Creates note; calls sns_edit() ( -c) -# sns_edit Decrypts note to /tmp, calls editor, re-encrypts note ( -e) -# sns_print Prints note to stdout ( -p) -# sns_rm Deletes note from store ( -d) -# sns_gitPassthrough Passes through all instructions to git (git) - -function sns_printError(){ - printf "$SNS_RED_COLOR!$SNS_RESET_COLOR - %s\n" "$@" -} -function sns_NoteHeader(){ - printf "%s\n%s\n" "Title:" "Date:" -} -function sns_checkDeps(){ - local SNS_RETURN="true"; - for DEP in "${SNS_DEPS[@]}"; do - if test ! -e "$(which "$DEP" 2>/dev/null)"; then - MISSING_DEPS+=("$DEP") - SNS_RETURN="false"; - fi - done - "$SNS_RETURN"; -} -function sns_checkStore(){ - if [ -d "$SNS_STORE" ]; then true; else false; fi -} -function sns_sanityCheck { - set +u - if [ "$SNS_EDITOR" != "vim" ] && [ -z "$EDITOR" ]; then - echo "$SNS_EDITOR" - sns_printError "No editor specified in environment." - SNS_EXIT="$SNS_ERR_NO_EDTR" - return - fi - set -u - if ! sns_checkDeps; then - SNS_EXIT="$SNS_ERR_DEPS" - for DEP in "${MISSING_DEPS[@]}"; do - local SNS_DEP_EC="\$ERR_NO_$DEP" - sns_printError "Dependency %s not in path." "$DEP"; - SNS_EXIT+=${!SNS_DEP_EC} - done - fi - - if [ -r "$SNS_STORE/.pubkey" ]; then - SNS_PUBKEY="$(cat "$SNS_STORE/.pubkey")" - elif ! sns_checkStore; then - if [ "$(echo "${SNS_ACTION[@]}" | awk '{print $1;}')" != "sns_initStore" ]; then - sns_printError "The sns store does not exist." - printf " - %s\n" "Please run \`sns -i [gpg-key]\` to initialize sns." - SNS_EXIT="$SNS_ERR_NO_STORE" - fi - fi -} -#---- -function sns_initStore { - mkdir -p "$SNS_STORE" - echo "$@" | awk '{print $1}' > "$SNS_KEYFILE" -} -function sns_printHelp(){ - printf "%s" "usage: sns [-cedlp] " - printf "\n%s%s%s" "usage: sns " "git" " ..." - printf "\n%s" " sns [-hi]" - - printf "\n%s" " -c | --create : Create note" - printf "\n%s" " -C | --config : Edit Config" - printf "\n%s" " -d | --delete : Delete note" - printf "\n%s" " -e | --edit : Open note for editing" - printf "\n%s" " -h | --help : Display this message" - printf "\n%s" " -i | --init : Write default config and initalize SNS store" - printf "\n%s" " -l | --list : List all notes in NOTEBOOK" - printf "\n%s" " -p | --print : Print note to console" - printf "\n\n" -} -function sns_list(){ - # Change directories to fix tree header - cd "$SNS_STORE" || exit; cd .. || exit; - # Print the tree - tree -C --noreport --prune "$(echo "$@" | awk '{print $1}')" -} - -function sns_rm(){ - rm -f "$SNS_STORE/$(echo "$@" | awk '{print $1}')" -} -function sns_gitPassthrough(){ - cd "$SNS_STORE" || exit; - git "$@"; -} -# ---- -function sns_argParse(){ - ARGS=($@) - #echo "${#ARGS}" - SNS_ACTION=() - while getopts ":i:hc:e:p:d:" OPT; do - - # If an option requiring an argument was passed without an argument, - # inform the user and set exit code to "$SNS_ERR_NS_ARGS" - case "${ARGS[!OPTIND]}" in - -i|-c|-e|-p|-d) - if [ "${#ARGS}" -lt 2 ]; then - sns_printError "A required argument was not given." - SNS_EXIT="$SNS_ERR_NS_ARGS" - return - fi - esac - case "$OPT" in - i) - SNS_ACTION=("sns_initStore" "$OPTARG");; - h) - SNS_ACTION=("sns_printHelp");; - c) - SNS_ACTION=("sns_create $OPTARG");; - e) - SNS_ACTION=("sns_edit $OPTARG");; - p) - SNS_ACTION=("sns_print $OPTARG");; - d) - SNS_ACTION=("sns_rm $OPTARG");; - esac - done - - if [ "${#SNS_ACTION[@]}" -eq 0 ]; then - if [ "$(echo "$@" | awk '{print $1}')" == "git" ]; then - SNS_ACTION=($@) - else - if [ -d "$SNS_STORE/$*" ]; then - SNS_ACTION=("sns_list" "sns/$@") - else - SNS_ACTION=("sns_list" "sns") - fi - fi - fi -} - -# Entry Point -printf "%s\n%s\n" "$XL_PRODUCT" "$XL_VER" -printf "\n" - -#Determine Run Mode -if [ -r "$SNS_STORE/.vim" ]; then - # Vim mode - readonly SNS_EDITOR="vim" - readonly SNS_DEPS=("vim" "tree" "git") - function sns_create(){ - if [ -r "$SNS_STORE/$(echo "$@" | awk '{print $1}')" ]; then - sns_printError "Note already exists." - return - fi - vim -x "$SNS_STORE/$(echo "$@" | awk '{print $1}')" - } - - function sns_edit(){ - vim "$SNS_STORE/$(echo "$@" | awk '{print $1}')" - } - - function sns_print(){ - sns_printError "Printing of Vim-encrypted notes is not supported at this time." - } -else - readonly SNS_EDITOR="$EDITOR" - readonly SNS_DEPS=("gpg2" "tree" "git") - - function sns_create(){ - # Make sure the note doesn't already exist - if [ -r "$SNS_STORE/$(echo "$@" | awk '{print $1}')" ]; then - sns_printError "Note already exists." - return - fi - - # Print the standard header to a temporary note - sns_NoteHeader > /tmp/"$SNS_SID" - - # Edit the new note - sns_edit "$(echo "$@" | awk '{print $1}')" - } - - function sns_edit(){ - # Make the function more readable - local readonly SNS_NOTE="$SNS_STORE/$(echo "$@" | awk '{print $1}')" - - # Test if edit was called from create - if [ ! -r /tmp/"$SNS_SID" ]; then gpg2 -d -o /tmp/"$SNS_SID" "$SNS_NOTE"; fi - - # Edit the note - "$EDITOR" /tmp/"$SNS_SID" - - # Make sure the notebook/section exists - if [ ! -d "$(dirname "$SNS_NOTE")" ]; then mkdir -p "$(dirname "$SNS_NOTE")"; fi - - # If the note previously existed, make a backup. - if [ -r "$SNS_NOTE" ]; then mv "$SNS_NOTE" "$SNS_NOTE.bk"; fi - - # Re-encrypt it to the store - gpg2 -r "$SNS_PUBKEY" -o "$SNS_NOTE" -e /tmp/"$SNS_SID" - - # If all went well, remove the backup - if [ -r "$SNS_NOTE" ]; then rm "$SNS_NOTE.bk"; fi - } - - function sns_print(){ - gpg2 -d "$SNS_STORE/$(echo "$@" | awk '{print $1}')" - } -fi - - -sns_argParse "$@" -sns_sanityCheck -if [ "$SNS_EXIT" -eq 0 ]; then - eval "${SNS_ACTION[@]}" -fi - -printf "\n" - -exit "$SNS_EXIT" diff --git a/vns.sh b/vns.sh new file mode 100755 index 0000000..ee392e9 --- /dev/null +++ b/vns.sh @@ -0,0 +1,218 @@ +#!/bin/bash +XL_PRODUCT="Vim Note System" +XL_VER="v2b" + +# Environment Constants +readonly VNS_STORE="$HOME/.local/vns" +readonly VNS_KEYFILE="$VNS_STORE/.pubkey" +readonly IFS=$'\n\t' +set -euo pipefail + +readonly VNS_RED_COLOR='\033[1;31m' +readonly VNS_YELLOW_COLOR='\033[1;33m' +readonly VNS_RESET_COLOR='\033[0m' + +# Erroneous Exit Constants + + # Invocation Errors + readonly VNS_ERR_NO_STORE=5 # The vns store needs to be initialized + readonly VNS_ERR_NO_OPTS=10 # No mode argument was specified + readonly VNS_ERR_NS_ARGS=11 # The specified mode requires an argument + readonly VNS_ERR_NO_EDTR=15 # No editor specified in environment + # Dependency Errors + readonly VNS_ERR_DEPS=20 # Base error - add the following codes + readonly VNS_ERR_NO_gpg2=1 # GPG is not installed + readonly VNS_ERR_NO_tree=2 # Tree is not installed + readonly VNS_ERR_NO_git=3 # Git is not installed + +# Global Variables +typeset -i VNS_SID="$RANDOM" +typeset -a VNS_ACTION=("") +typeset -a MISSING_DEPS +typeset -i VNS_EXIT=0 +declare VNS_PUBKEY= +# Function Definitions +# vns_printError Prints an error message +# vns_NoteHeader Prints standard note header to stdout +# vns_checkDeps Checks the system for required dependencies +# vns_checkStore Checks if $VNS_STORE exists +# vns_sanityCheck Wrapper for vns_checkDeps and vns_checkStore +# ---- +# vns_initStore Initializes the VNS Store ( -i) +# vns_printHelp Prints help page ( -h) +# vns_list Lists all notes in `tree` format ( *) +# vns_create Creates note; calls vns_edit() ( -c) +# vns_edit Decrypts note to /tmp, calls editor, re-encrypts note ( -e) +# vns_print Prints note to stdout ( -p) +# vns_rm Deletes note from store ( -d) +# vns_gitPassthrough Passes through all instructions to git (git) + +function vns_printError(){ + printf "$VNS_RED_COLOR!$VNS_RESET_COLOR - %s\n" "$@" +} +function vns_NoteHeader(){ + printf "%s\n%s\n" "Title:" "Date:" +} +function vns_checkDeps(){ + local VNS_RETURN="true"; + for DEP in "${VNS_DEPS[@]}"; do + if test ! -e "$(which "$DEP" 2>/dev/null)"; then + MISSING_DEPS+=("$DEP") + VNS_RETURN="false"; + fi + done + "$VNS_RETURN"; +} +function vns_checkStore(){ + if [ -d "$VNS_STORE" ]; then true; else false; fi +} +function vns_sanityCheck { + set +u + if [ "$VNS_EDITOR" != "vim" ] && [ -z "$EDITOR" ]; then + echo "$VNS_EDITOR" + vns_printError "No editor specified in environment." + VNS_EXIT="$VNS_ERR_NO_EDTR" + return + fi + set -u + if ! vns_checkDeps; then + VNS_EXIT="$VNS_ERR_DEPS" + for DEP in "${MISSING_DEPS[@]}"; do + local VNS_DEP_EC="\$ERR_NO_$DEP" + vns_printError "Dependency %s not in path." "$DEP"; + VNS_EXIT+=${!VNS_DEP_EC} + done + fi + + if [ -r "$VNS_STORE/.pubkey" ]; then + VNS_PUBKEY="$(cat "$VNS_STORE/.pubkey")" + elif ! vns_checkStore; then + if [ "$(echo "${VNS_ACTION[@]}" | awk '{print $1;}')" != "vns_initStore" ]; then + vns_printError "The vns store does not exist." + printf " - %s\n" "Please run \`vns -i [gpg-key]\` to initialize vns." + VNS_EXIT="$VNS_ERR_NO_STORE" + fi + fi +} +#---- +function vns_initStore { + mkdir -p "$VNS_STORE" + echo "$@" | awk '{print $1}' > "$VNS_KEYFILE" +} +function vns_printHelp(){ + printf "%s" "usage: vns [-cedlp] " + printf "\n%s%s%s" "usage: vns " "git" " ..." + printf "\n%s" " vns [-hi]" + + printf "\n%s" " -c | --create : Create note" + printf "\n%s" " -C | --config : Edit Config" + printf "\n%s" " -d | --delete : Delete note" + printf "\n%s" " -e | --edit : Open note for editing" + printf "\n%s" " -h | --help : Display this message" + printf "\n%s" " -i | --init : Write default config and initalize VNS store" + printf "\n%s" " -l | --list : List all notes in NOTEBOOK" + printf "\n%s" " -p | --print : Print note to console" + printf "\n\n" +} +function vns_list(){ + # Change directories to fix tree header + cd "$VNS_STORE" || exit; cd .. || exit; + # Print the tree + tree -C --noreport --prune "$(echo "$@" | awk '{print $1}')" +} + +function vns_rm(){ + rm -f "$VNS_STORE/$(echo "$@" | awk '{print $1}')" +} +function vns_gitPassthrough(){ + cd "$VNS_STORE" || exit; + git "$@"; +} +# ---- +function vns_argParse(){ + ARGS=($@) + #echo "${#ARGS}" + VNS_ACTION=() + while getopts ":i:hc:e:p:d:" OPT; do + + # If an option requiring an argument was passed without an argument, + # inform the user and set exit code to "$VNS_ERR_NS_ARGS" + case "${ARGS[!OPTIND]}" in + -i|-c|-e|-p|-d) + if [ "${#ARGS}" -lt 2 ]; then + vns_printError "A required argument was not given." + VNS_EXIT="$VNS_ERR_NS_ARGS" + return + fi + esac + case "$OPT" in + i) + VNS_ACTION=("vns_initStore" "$OPTARG");; + h) + VNS_ACTION=("vns_printHelp");; + c) + VNS_ACTION=("vns_create $OPTARG");; + e) + VNS_ACTION=("vns_edit $OPTARG");; + p) + VNS_ACTION=("vns_print $OPTARG");; + d) + VNS_ACTION=("vns_rm $OPTARG");; + esac + done + + if [ "${#VNS_ACTION[@]}" -eq 0 ]; then + if [ "$(echo "$@" | awk '{print $1}')" == "git" ]; then + VNS_ACTION=($@) + else + if [ -d "$VNS_STORE/$*" ]; then + VNS_ACTION=("vns_list" "vns/$@") + else + VNS_ACTION=("vns_list" "vns") + fi + fi + fi +} + +# Entry Point +printf "%s\n%s\n" "$XL_PRODUCT" "$XL_VER" +printf "\n" +readonly VNS_EDITOR="vim" +readonly VNS_DEPS=("vim" "gpg2" "tree" "git") + + function vns_create(){ + VNS_NOTE="$VNS_STORE/$(echo "$@" | awk '{print $1}').gpg" + # Make sure the note doesn't already exist + if [ -r "$VNS_NOTE" ]; then + vns_printError "Note already exists." + else + if [ ! -d "$(dirname $VNS_NOTE)" ]; then + mkdir -p "$(dirname $VNS_NOTE)" + fi + vns_NoteHeader | gpg2 -e -r "$VNS_PUBKEY" -o "$VNS_NOTE" + vns_edit "$@" + fi +} + + function vns_edit(){ + # Make the function more readable + local readonly VNS_NOTE="$VNS_STORE/$(echo "$@" | awk '{print $1}').gpg" + # Edit the note + vim "$VNS_NOTE" +} + +function vns_print(){ + gpg2 -d "$VNS_STORE/$(echo "$@" | awk '{print $1}')" +} + + + +vns_argParse "$@" +vns_sanityCheck +if [ "$VNS_EXIT" -eq 0 ]; then + eval "${VNS_ACTION[@]}" +fi + +printf "\n" + +exit "$VNS_EXIT"