224 lines
7.2 KiB
Bash
Executable file
224 lines
7.2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
# Trap EXIT signal to clean up by default
|
|
cleanup() {
|
|
echo "Cleaning up temporary directory..."
|
|
rm -rf "$BWZY_CACHE"
|
|
}
|
|
[[ "$BWZY_KEEP_CACHE" != 'true' ]] && trap cleanup EXIT
|
|
|
|
# defaults
|
|
BWZY_CACHE="${BWZY_CACHE:-/dev/shm/bwzy-cache}"
|
|
BWZY_FILTER="${BWZY_FILTER:-zz~ }"
|
|
# options passed to fzf for default and oneshot modes
|
|
BWZY_DEFAULT_OPTS=(--layout reverse --height 100%)
|
|
BWZY_ONESHOT_OPTS=(--layout default --height 10 --no-header)
|
|
# visual settings
|
|
BWZY_POINTER_SYMBOL=${BWZY_POINTER_SYMBOL:-> }
|
|
BWZY_PROMPT_SYMBOL=${BWZY_PROMPT_SYMBOL:-? }
|
|
BWZY_USER_SYMBOL=${BWZY_USER_SYMBOL:-u+}
|
|
BWZY_PASS_SYMBOL=${BWZY_PASS_SYMBOL:-p+}
|
|
BWZY_TOTP_SYMBOL=${BWZY_TOTP_SYMBOL:-t+}
|
|
BWZY_LINK_SYMBOL=${BWZY_LINK_SYMBOL:-l+}
|
|
BWZY_AUTO_SYMBOL=${BWZY_AUTO_SYMBOL:-a+}
|
|
BWZY_FOLDER_SYMBOL=${BWZY_FOLDER_SYMBOL:-/}
|
|
|
|
# these are needed by the auto-completer
|
|
export BWZY_COPY_CMD=${BWZY_COPY_CMD:-wl-copy}
|
|
export BWZY_TYPE_CMD=${BWZY_TYPE_CMD:-wtype}
|
|
|
|
# and the auto-completer itself
|
|
export BWZY_AUTOFILL_HELPER=${BWZY_AUTOFILL_HELPER:-$(dirname "$(realpath "$0")")/bwzy-autofill}
|
|
export BWZY_TOTP_COPY=${BWZY_TOTP_COPY:-$(dirname "$(realpath "$0")")/bwzy-copy-totp}
|
|
|
|
# set | rg BWZY
|
|
# exit 0
|
|
|
|
read -r -d '' HELP_TEXT<<'EOH'
|
|
bwzy is a fuzzy wrapper to the bitwarden cli
|
|
|
|
Usage:
|
|
`bwzy` will run the fuzzy selection
|
|
`bwzy -1` exit after first action
|
|
`bwzy -a` show all items (no filter)
|
|
`bwzy -s` syncs the vault
|
|
`bwzy -f` force syncs the vault (implies `-s`)
|
|
`bwzy -c` clears the cache
|
|
`bwzy -d` enables debug logging
|
|
`bwzy -h` shows this help
|
|
EOH
|
|
|
|
# set some defaults to override based on options
|
|
BWZY_OPTS=("${BWZY_DEFAULT_OPTS[@]}")
|
|
CLEAR_CACHE='false'
|
|
DEBUG='false'
|
|
FORCE_SYNC='false'
|
|
ONESHOT='false'
|
|
SYNC_CACHE='false'
|
|
FILTER_ARG='-v' # argument to grep
|
|
|
|
# read in the options
|
|
while getopts "acdfhis1" o; do
|
|
case "${o}" in
|
|
1)
|
|
ONESHOT='true'
|
|
BWZY_OPTS=( "${BWZY_ONESHOT_OPTS[@]}" )
|
|
;;
|
|
a)
|
|
BWZY_FILTER=''
|
|
FILTER_ARG=''
|
|
;;
|
|
c)
|
|
CLEAR_CACHE='true'
|
|
;;
|
|
d)
|
|
DEBUG='true'
|
|
;;
|
|
f)
|
|
FORCE_SYNC='true'
|
|
SYNC_CACHE='true'
|
|
;;
|
|
h)
|
|
# show help and exit
|
|
( echo "$HELP_TEXT"
|
|
echo -e "\n# active settings:"
|
|
echo '```bash'
|
|
set | grep '^BWZY_' | sort
|
|
echo '```'
|
|
) | bat -l markdown --plain
|
|
exit 0
|
|
;;
|
|
s)
|
|
SYNC_CACHE='true'
|
|
;;
|
|
*)
|
|
usage
|
|
;;
|
|
esac
|
|
done
|
|
shift $((OPTIND-1))
|
|
|
|
query=$*
|
|
|
|
# begin of main code
|
|
function log() {
|
|
[[ $DEBUG == 'true' ]] && gum log "$@"
|
|
}
|
|
log "bwzy options: flush:${CLEAR_CACHE} sync:${SYNC_CACHE} force:${FORCE_SYNC}"
|
|
|
|
# Create temporary directories in tmpfs
|
|
# Static filename is used to allow for re-use between invocations
|
|
umask 077 # only user shall have permissions
|
|
[[ "$CLEAR_CACHE" == 'true' ]] \
|
|
&& rm -rf "$BWZY_CACHE" \
|
|
&& echo "cache flushed" && exit 0
|
|
[[ ! -d $BWZY_CACHE ]] && mkdir "$BWZY_CACHE"
|
|
[[ ! -d $BWZY_CACHE ]] && echo "failed to create tmp dir" && exit 1
|
|
|
|
# use the session token if it exists
|
|
if [[ -f ${BWZY_CACHE}/session ]]; then
|
|
BW_SESSION=$(<"${BWZY_CACHE}/session")
|
|
export BW_SESSION
|
|
fi
|
|
|
|
if [[ ! -f "${BWZY_CACHE}/items" ]] || [[ ! -s "${BWZY_CACHE}/items" ]] \
|
|
|| [[ ! -f "${BWZY_CACHE}/folders" ]] || [[ ! -s "${BWZY_CACHE}/folders" ]] \
|
|
|| [[ $SYNC_CACHE == 'true' ]]; then
|
|
|
|
# test the session token and get a new one if it's not unlocked
|
|
if [[ -z "$BW_SESSION" ]] || [[ $(bw status | jq -r '.status') != 'unlocked' ]]; then
|
|
BW_PASS=$(gum input --placeholder="enter master password to unlock vault ..." --password)
|
|
BW_SESSION=$(bw unlock "$BW_PASS" --raw)
|
|
[[ -z $BW_SESSION ]] && echo "failed to get session token" && exit 1
|
|
export BW_SESSION
|
|
fi
|
|
|
|
# force the full sync
|
|
[[ $FORCE_SYNC == 'true' ]] && gum spin --title 'forcing full sync' -s dot -- bw sync -f
|
|
|
|
echo "$BW_SESSION" > "${BWZY_CACHE}/session"
|
|
gum spin --title 'fetching items ...' -s dot bw list items > "${BWZY_CACHE}/items"
|
|
gum spin --title 'fetching folder list ...' -s dot bw list folders > "${BWZY_CACHE}/folders"
|
|
fi
|
|
|
|
log "bwzy Environment:"
|
|
[[ $DEBUG == 'true' ]] && set | grep ^BWZY | bat -l sh | while read -r line; do
|
|
log -- "- $line"
|
|
done
|
|
|
|
items="${BWZY_CACHE}/items"
|
|
folders="${BWZY_CACHE}/folders"
|
|
|
|
TAB=" "
|
|
folder_sed=$(jq -r '.[] | [ "s@" , .id , "@" , .name , "@;" ] | join("")' < "$folders")
|
|
|
|
# define the logic here for use in fzf call
|
|
jq_select_id="jq -r '.[] | select(.id == \"{1}\")"
|
|
|
|
pre_action="execute-silent($BWZY_REFOCUS_CMD && $BWZY_HIDE_CMD)"
|
|
|
|
# set up queries for the fields
|
|
select_user="$jq_select_id | .login.username' <$items"
|
|
select_pass="$jq_select_id | .login.password' <$items"
|
|
select_totp="$jq_select_id | .login.totp' <$items"
|
|
select_link="$jq_select_id | .login.uris[1].uri' <$items"
|
|
select_user_pass_totp="$select_user; $select_pass; $select_totp"
|
|
|
|
if [[ $ONESHOT == 'true' ]]; then
|
|
copy_user="become($select_user)"
|
|
copy_pass="become($select_pass)"
|
|
copy_totp="become($select_totp)"
|
|
copy_link="become($select_link)"
|
|
auto_paste="become(($select_user_pass_totp))"
|
|
else
|
|
copy_user="execute-silent($select_user | $BWZY_COPY_CMD)"
|
|
copy_pass="execute-silent($select_pass | $BWZY_COPY_CMD)"
|
|
copy_totp="execute-silent($select_totp | $BWZY_TOTP_COPY)"
|
|
copy_link="execute-silent($select_link | $BWZY_COPY_CMD)"
|
|
auto_paste="execute-silent(($select_user_pass_totp) | $BWZY_AUTOFILL_HELPER)"
|
|
|
|
copy_user="${pre_action}+${copy_user}+change-prompt($BWZY_USER_SYMBOL)"
|
|
copy_pass="${pre_action}+${copy_pass}+change-prompt($BWZY_PASS_SYMBOL)"
|
|
copy_totp="${pre_action}+${copy_totp}+change-prompt($BWZY_TOTP_SYMBOL)"
|
|
copy_link="${pre_action}+${copy_link}+change-prompt($BWZY_LINK_SYMBOL)"
|
|
auto_paste="${pre_action}+${auto_paste}+change-prompt($BWZY_AUTO_SYMBOL)"
|
|
fi
|
|
|
|
preview_item="$jq_select_id' < $items | json2yaml | bat --color=always -p -l yaml"
|
|
|
|
read -r -d '' fzf_header <<FZF_HEADER
|
|
[a-.] preview on/off [a-/] preview wrap [a-?] this help
|
|
[a-u] copy username [a-p] copy password [a-t] copy totp
|
|
[a-l] copy link [enter] auto-fill [esc] clear/quit
|
|
FZF_HEADER
|
|
|
|
# | awk -F "$TAB" '{ printf "%s\t%-35s\t%27s\n", $1, $2, $3 }' \
|
|
# | awk -F ' ' '{ print $1"\t{{ Color \"3\" \"0\" \""$2"\" }}\t{{Color \"8\" \"0\" \"" $3 "\" }}" }' \
|
|
#
|
|
# shellcheck disable=SC2016
|
|
# **the single and double quotes in the fzf commands are intentional**
|
|
jq -r '.[] | [ .id, .name, "`'"$BWZY_FOLDER_SYMBOL"'" + .folderId + "`" ] | join("'"$TAB"'")' <"$items" \
|
|
| sed -e "$folder_sed" \
|
|
| grep ${FILTER_ARG} "${BWZY_FILTER}" \
|
|
| bat -l markdown --color=always --style=plain \
|
|
| fzf --ansi -i --delimiter="$TAB" --with-nth 2,3 \
|
|
--header-first \
|
|
--marker='' --pointer="$BWZY_POINTER_SYMBOL" \
|
|
--prompt="$BWZY_PROMPT_SYMBOL" \
|
|
--info 'inline' \
|
|
--info-command 'echo "($FZF_MATCH_COUNT/$FZF_TOTAL_COUNT)"' \
|
|
--bind "alt-u:$copy_user" \
|
|
--bind "alt-p:$copy_pass" \
|
|
--bind "alt-t:$copy_totp" \
|
|
--bind "alt-l:$copy_link" \
|
|
--bind "enter:$auto_paste" \
|
|
--bind 'alt-,:toggle-preview' \
|
|
--bind 'alt-/:toggle-preview-wrap' \
|
|
--bind 'alt-?:toggle-header' \
|
|
--bind 'esc:cancel' \
|
|
--bind 'focus:transform-preview-label:echo {2} / {3}' \
|
|
--bind "focus:change-prompt($BWZY_PROMPT_SYMBOL)" \
|
|
--preview-window 'down:75%:hidden' \
|
|
--preview "$preview_item" \
|
|
--header "${fzf_header}" \
|
|
"${BWZY_OPTS[@]}" -q "$query"
|