#!/bin/sh # Copyright 2025 Mitchell Riedstra # # Permission to use, copy, modify, and/or distribute this software for any purpose # with or without fee is hereby granted, provided that the above copyright notice # and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND # FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF # THIS SOFTWARE. set -e PASSWORD_STORE_X_SELECTION="${PASSWORD_STORE_X_SELECTION:-clipboard}" PASSWORD_STORE_CLIP_TIME="${PASSWORD_STORE_CLIP_TIME:-45}" DPW_BACKEND="${DPW_BACKEND:-dpw-gpg}" OTP="${OATH:-oathtool} -b --totp" _clip() { case $(uname) in Darwin) pbcopy ;; *) case $XDG_SESSION_TYPE in wayland) wl-copy ;; *) xclip -selection "$PASSWORD_STORE_X_SELECTION" ;; esac :; esac } _clip_clear() { case $(uname) in Darwin) sh -c "sleep ${PASSWORD_STORE_CLIP_TIME}; printf '' | pbcopy" & ;; *) case $XDG_SESSION_TYPE in wayland) sh -c "sleep ${PASSWORD_STORE_CLIP_TIME}; wl-copy -c " & ;; *) sh -c "sleep ${PASSWORD_STORE_CLIP_TIME}; printf '' | xclip -selection '$PASSWORD_STORE_X_SELECTION'" & ;; esac :; esac } _printH() { printf "%-25s # %s\n" "$@" } help() { cat <" "dumps secret to stdout" _printH "copy " "copies the first line of the secret to the clipboard" _printH "copyf " "copies the entire secret to the clipboard" _printH "type " "types the first line of the secret with xdotool" _printH "list []" "dumps a listing of all subkeys if key is supplied" _printH "ls []" "Same as above" _printH "mv " "Moves key a to b, conflict handling depends on backend" _printH "cp " "Copies key a to b, conflict handling depends on backend" _printH "rm " "Removes an entry, additional flags are passed to the backend" _printH "_backend ..." "Directly calls the configured backend, most of the time not needed but occasionally useful" _printH "sync" "Calls sync command for the particular backend, may not be implemented" _printH "reinsert " "Re-inserts the key, useful for backends that don't rotate keys until you remove/reinsert" _printH "edit " "Edits with your \$EDITOR($EDITOR). Saves to a tmpfile removes when complete" _printH "otp " "Generate otp from otpauth:// URL in a secret ( requires: oathtool ) " _printH "otp show " "Same as above" _printH "otp copy " "Same as above, but copies to the clipboard" _printH "otp type " "Same as above, but types it out" _printH "fnd " "runs 'grep' with the arg over all the keys from 'list' " _printH "find " "same as above" _printH "insert [-i] [-m] " "-i for interactive, -m for multiline/stdin mode" cat < "\$_f" #shellcheck disable=SC1090 . "\$_f" rm -f "\$_f" } Just be careful, that will lead your shell to executaing whatever the contents of that particular entry are. EOF exit 0 } insert_interactive() { pth="$1"; shift printf "Enter password: " stty -echo; read -r passwd; stty echo printf "\nEnter password again: " stty -echo; read -r passwd2; stty echo echo "" if [ "$passwd" != "$passwd2" ] ; then echo "Passwords do not match" exit 1 fi echo "$passwd" | "${DPW_BACKEND}" insert "$pth" } insert() { pth="$1"; shift "${DPW_BACKEND}" insert "$pth" } show() { pth="$1"; shift "${DPW_BACKEND}" show "$pth" } _find() { pth="$1"; shift "${DPW_BACKEND}" list | grep -iE "$pth" } copy() { pth="$1"; shift "${DPW_BACKEND}" show "$pth" \ | sed 1q \ | tr -d '\n' \ | _clip _clip_clear } copyf() { pth="$1"; shift "${DPW_BACKEND}" show "$pth" \ | _clip _clip_clear } __type() { case $XDG_SESSION_TYPE in wayland) wtype -s 2 - ;; *) xdotool type --delay 2ms --clearmodifiers --file - ;; esac } _type() { pth="$1"; shift "${DPW_BACKEND}" show "$pth" \ | sed 1q \ | tr -d '\n' \ | __type } list() { "${DPW_BACKEND}" list "$1" } _cp() { a="$1"; shift b="$1"; shift "${DPW_BACKEND}" show "$a" | "${DPW_BACKEND}" insert "$b" } move() { a="$1"; shift b="$1"; shift _cp "$a" "$b" remove "$a" } remove() { "${DPW_BACKEND}" rm "$@" } _backend() { "${DPW_BACKEND}" "$@" } _sync() { "${DPW_BACKEND}" sync "$@" } reinsert() { _pth="$1"; shift tmpdir=/dev/shm if ! [ -d "$tmpdir" ] ; then printf "Your system does not have /dev/shm, continue? [Yy] " read -r resp ok=0 case $resp in Y*|y*) ok=1 esac echo "" [ $ok -eq 0 ] && return tmpdir=/tmp fi _f="$(mktemp "${tmpdir}/dpw.XXXXXXXXXX")" #shellcheck disable=SC2064 trap "rm -f \"$_f\"; exit 0" EXIT INT show "$_pth" > "$_f" remove "$_pth" insert "$_pth" < "$_f" rm -f "$_f" } edit() { _pth="$1"; shift tmpdir=/dev/shm if ! [ -d "$tmpdir" ] ; then printf "Your system does not have /dev/shm, continue? [Yy] " read -r resp ok=0 case $resp in Y*|y*) ok=1 esac echo "" [ $ok -eq 0 ] && return tmpdir=/tmp fi _f="$(mktemp "${tmpdir}/dpw.XXXXXXXXXX")" #shellcheck disable=SC2064 trap "rm -f \"$_f\"; exit 0" EXIT INT show "$_pth" > "$_f" ${EDITOR:-vi} "$_f" remove "$_pth" insert "$_pth" < "$_f" rm -f "$_f" } otp() { a=show _pth="$1" if [ $# -gt 1 ] ; then #shellcheck disable=SC2209 case $1 in show) a=show ; shift ;; copy) a=copy ; shift ;; type) a=type ; shift ;; *) echo "unknown option: $1" ; exit 1 ;; esac _pth="$1" fi if [ -z "$_pth" ] ; then echo "Invalid path" exit 1 fi authstring="$(show "$_pth" \ | grep "otpauth" \ | sed 1q \ | grep -oE 'secret=[^&]*' \ | sed -e's/secret=//')" if [ -z "$authstring" ] ; then echo "No OTP secret found" exit 1 fi case $a in show) $OTP "$authstring" ;; copy) $OTP "$authstring" \ | tr -d '\n' \ | _clip _clip_clear ;; type) $OTP "$authstring" \ | tr -d '\n' \ | __type ;; esac } init() { "${DPW_BACKEND}" init "$@" } if [ -z "$1" ] ; then list "$@" exit 0 fi action=show case $1 in insert) action=insert; shift; if [ "$1" = "-m" ] ; then shift; fi if [ "$1" = "-i" ] ; then action=insert_interactive shift fi ;; show) shift ;; copy) action=copy; shift ;; copyf) action=copyf; shift ;; type) action=_type; shift ;; list|ls) action=list; shift ;; rm) action=remove; shift ;; mv) action=move; shift ;; cp) action=_cp; shift ;; sync) action=_sync; shift ;; _backend) action=_backend; shift ;; reinsert) action=reinsert; shift ;; edit) action=edit; shift ;; find|fnd) action=_find; shift ;; otp) action=otp; shift ;; init) action=init; shift ;; -h|--help|help) help ;; esac "$action" "$@"