355 lines
9.4 KiB
Bash
Executable File
355 lines
9.4 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# passrofi -- Rofi script for pass (passwordstore.org)
|
|
# Version: 0.1.1
|
|
# Author: binaryDiv
|
|
|
|
|
|
# Strict mode
|
|
set -euo pipefail
|
|
IFS=$'\n\t'
|
|
|
|
# Set some generic variables
|
|
SCRIPTNAME=$0
|
|
BASENAME=$(basename $0)
|
|
|
|
|
|
# Usage
|
|
# -----
|
|
|
|
usage() {
|
|
cat << END_OF_USAGE
|
|
Usage: $BASENAME [-d DIR] [-p PROMPT]
|
|
|
|
Simple rofi script to query passwords from pass (passwordstore.org).
|
|
|
|
Calling this script directly will start rofi in script mode.
|
|
|
|
General options:
|
|
-h, --help Print this help message
|
|
-v, --verbose Enables debug logging via stderr
|
|
-d DIR Sets the password-store directory (default: ~/.password-store)
|
|
-p PROMPT Sets the rofi prompt to PROMPT (default: "Password")
|
|
END_OF_USAGE
|
|
}
|
|
|
|
|
|
# Helper functions
|
|
# ----------------
|
|
|
|
# Logs a message to stderr if verbose mode is enabled (with a '#' prefix)
|
|
log() {
|
|
if [[ -n $PASSROFI_VERBOSE ]]; then
|
|
echo "# $@" >&2
|
|
fi
|
|
}
|
|
|
|
# Prints a message to stderr and exits with status 1
|
|
fail() {
|
|
echo "$@" >&2
|
|
exit 1
|
|
}
|
|
|
|
|
|
# Main entrypoint
|
|
# ---------------
|
|
|
|
# Main entrypoint: Checks if the script was called from the command line or as a rofi script, then
|
|
# calls either main_cli (first case) or main_rofi (second case).
|
|
main() {
|
|
# Set defaults for environment variables
|
|
set_default_env
|
|
|
|
# If the script was called by rofi, this env variable will be defined
|
|
if [[ -v ROFI_RETV ]]; then
|
|
main_rofi "$@"
|
|
else
|
|
main_cli "$@"
|
|
fi
|
|
}
|
|
|
|
set_default_env() {
|
|
# Set default values for environment variables (unless already set)
|
|
PASSWORD_STORE_DIR=${PASSWORD_STORE_DIR:-~/.password-store}
|
|
PASSROFI_VERBOSE=${PASSROFI_VERBOSE:-}
|
|
PASSROFI_PROMPT=${PASSROFI_PROMPT:-Password}
|
|
}
|
|
|
|
|
|
# CLI mode (called from command line)
|
|
# -----------------------------------
|
|
|
|
# Entrypoint when called from the command line: Parses command line arguments, sets some environment
|
|
# variables, and starts rofi (which then calls this script again, using main_rofi as entrypoint).
|
|
main_cli() {
|
|
# Parse command line arguments (sets variables)
|
|
parse_cli_args "$@"
|
|
|
|
# Start rofi
|
|
exec_rofi
|
|
}
|
|
|
|
# Parses command line arguments and sets variables accordingly
|
|
parse_cli_args() {
|
|
# Use "-:" to sort of parse long options
|
|
while getopts ":hvd:p:-:" option; do
|
|
# getopts does not support long options, so we build a workaround
|
|
if [[ $option == "-" ]]; then
|
|
option="-$OPTARG"
|
|
OPTARG=""
|
|
fi
|
|
|
|
case $option in
|
|
h|-help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
|
|
v|-verbose)
|
|
export PASSROFI_VERBOSE=1
|
|
log "I can be very verbose if you want me to, HOOT HOOT"
|
|
;;
|
|
|
|
d)
|
|
export PASSWORD_STORE_DIR="$OPTARG"
|
|
log "Setting password store directory to $PASSWORD_STORE_DIR"
|
|
;;
|
|
|
|
p)
|
|
export PASSROFI_PROMPT="$OPTARG"
|
|
log "Setting rofi prompt to $PASSROFI_PROMPT"
|
|
;;
|
|
|
|
:)
|
|
fail "$BASENAME: option -$OPTARG requires an argument."
|
|
;;
|
|
|
|
?|*)
|
|
fail "$BASENAME: invalid option: -${OPTARG:-$option}"
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Executes rofi with the needed parameters and environment variables
|
|
exec_rofi() {
|
|
exec rofi -show pass \
|
|
-modi "pass:$SCRIPTNAME" \
|
|
-kb-custom-1 "Alt-h" \
|
|
-kb-custom-2 "Alt-u" \
|
|
-kb-custom-3 "Alt-v"
|
|
}
|
|
|
|
|
|
# Rofi script mode (called by rofi)
|
|
# ---------------------------------
|
|
|
|
# Entrypoint when called by rofi as a rofi script: Lists available password files and parses input.
|
|
main_rofi() {
|
|
# Debug output
|
|
log "Called by rofi: ROFI_RETV=${ROFI_RETV:-}"
|
|
log " ARGS: $@"
|
|
log " ROFI_INFO: ${ROFI_INFO:-}"
|
|
log " PASSWORD_STORE_DIR: ${PASSWORD_STORE_DIR:-}"
|
|
|
|
# Ensure all environment variables are set correctly, directories exist etc.
|
|
ensure_environment
|
|
|
|
# Set rofi mode options (outputs specially formatted strings)
|
|
set_rofi_mode_options
|
|
|
|
# Get user selection from arguments
|
|
USER_SELECTION="$@"
|
|
|
|
# Check current script state and decide which action to take next
|
|
handle_rofi_state
|
|
}
|
|
|
|
# Checks the current script state and executes the next action
|
|
handle_rofi_state() {
|
|
# First, check if we're coming from the help screen
|
|
if [[ ${ROFI_INFO:-} == 'help_menu' ]]; then
|
|
# Change state so that we show the password list again
|
|
ROFI_RETV=0
|
|
ROFI_INFO=""
|
|
fi
|
|
|
|
# Check state to determine what to do
|
|
case $ROFI_RETV in
|
|
# Initial call of script
|
|
0)
|
|
# Generate a list of password files for rofi to show
|
|
generate_password_list
|
|
;;
|
|
|
|
# User selected an entry from the list (1) or a custom entry (2)
|
|
1|2)
|
|
# Use pass to decrypt and copy the password
|
|
handle_user_selection
|
|
;;
|
|
|
|
# Custom keybinding 1 (Alt-h): Show help for keybindings
|
|
10)
|
|
show_help_menu
|
|
;;
|
|
|
|
# Custom keybinding 2 (Alt-u): Copy username
|
|
11)
|
|
# Copy username to clipboard
|
|
handle_copy_username
|
|
;;
|
|
|
|
# Custom keybinding 3 (Alt-v): View password file in terminal
|
|
12)
|
|
# Open password file in a less inside a terminal
|
|
handle_view_file
|
|
;;
|
|
|
|
# Unused custom keybindings: Show help menu
|
|
1[3-9]|2[0-8])
|
|
log "Unused keybinding $((ROFI_RETV - 9)), showing help instead."
|
|
show_help_menu
|
|
;;
|
|
|
|
# Unknown state
|
|
*)
|
|
fail "Unknown ROFI_RETV state: $ROFI_RETV"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Ensures all environment variables are set correctly
|
|
ensure_environment() {
|
|
# Check if the password store directory exists
|
|
[[ -d $PASSWORD_STORE_DIR ]] || fail "Invalid password store directory: $PASSWORD_STORE_DIR"
|
|
}
|
|
|
|
# Sets (outputs) rofi mode options
|
|
set_rofi_mode_options() {
|
|
# This enables custom keybindings for the script
|
|
echo -en "\0use-hot-keys\x1ftrue\n"
|
|
|
|
# Disable custom input
|
|
echo -en "\0no-custom\x1ftrue\n"
|
|
|
|
# Set default prompt and message
|
|
echo -en "\0prompt\x1f$PASSROFI_PROMPT\n"
|
|
echo -en "\0message\x1f\n"
|
|
}
|
|
|
|
# Custom keybinding (Alt-h) to show a help menu with keybindings
|
|
show_help_menu() {
|
|
# Set prompt and message
|
|
echo -en "\0prompt\x1fHelp\n"
|
|
echo -en "\0message\x1fThis is a list of custom keybindings for passrofi.\n"
|
|
|
|
# Helper function that adds an info string to the entry
|
|
_echo() {
|
|
echo -en "$@\0info\x1fhelp_menu\n"
|
|
}
|
|
|
|
# Print help page
|
|
_echo "Alt-h: Shows this help page"
|
|
_echo " "
|
|
_echo "Alt-u: Copy username"
|
|
_echo "Alt-v: View password file"
|
|
}
|
|
|
|
# Lists all password files as input for rofi
|
|
generate_password_list() {
|
|
# Get list of all .gpg files in password-store
|
|
password_store_dir="${PASSWORD_STORE_DIR%/}"
|
|
for password_file in $(find "$password_store_dir" -type f -name '*.gpg' | sort); do
|
|
# Strip prefix and suffix
|
|
password_file="${password_file#"$password_store_dir"/}"
|
|
password_file="${password_file%.gpg}"
|
|
|
|
# Output filename
|
|
echo $password_file
|
|
done
|
|
}
|
|
|
|
# Parse user selection: Use pass to decrypt and copy password
|
|
handle_user_selection() {
|
|
log "User selected entry: $USER_SELECTION"
|
|
|
|
# External programs need to be launched asynchronously, otherwise rofi blocks
|
|
coproc (
|
|
# Copy password to clipboard (pass will automatically clear the clipboard after 45 seconds)
|
|
pass show -c "$USER_SELECTION" &>/dev/null
|
|
)
|
|
|
|
# End here
|
|
exit 0
|
|
}
|
|
|
|
# Copys the username of a selected password file to the clipboard
|
|
handle_copy_username() {
|
|
log "User wants to copy username of: $USER_SELECTION"
|
|
|
|
# We can get the username directly from the filename
|
|
username=${USER_SELECTION##*/}
|
|
|
|
log "Username is: $username"
|
|
|
|
# Copy to clipboard
|
|
coproc (
|
|
echo "$username" | xclip -selection clipboard -r -silent &>/dev/null
|
|
)
|
|
|
|
# End here
|
|
exit 0
|
|
}
|
|
|
|
# Views the selected password file in a pager inside a terminal
|
|
handle_view_file() {
|
|
log "User wants to view file: $USER_SELECTION"
|
|
|
|
# Get full filename
|
|
password_store_dir="${PASSWORD_STORE_DIR%/}"
|
|
filename="$password_store_dir/$USER_SELECTION.gpg"
|
|
|
|
# Open file in a pager inside a terminal
|
|
coproc (
|
|
#rofi-sensible-terminal -e "bash -ic 'pass show \"$USER_SELECTION\" | less'" &>/dev/null
|
|
{
|
|
echo "Password file: $USER_SELECTION"
|
|
echo "------------------------------------------------------------"
|
|
pass show "$USER_SELECTION"
|
|
echo
|
|
} | view_stdin_in_terminal &>/dev/null
|
|
)
|
|
}
|
|
|
|
# Opens a terminal that shows data from stdin. Uses 'view_in_terminal' if it exists in $PATH.
|
|
view_stdin_in_terminal() {
|
|
if which view_in_terminal &>/dev/null; then
|
|
view_in_terminal
|
|
else
|
|
rofi-sensible-terminal -e "bash -ic 'less < /proc/$BASHPID/fd/0'"
|
|
fi
|
|
}
|
|
|
|
|
|
# Run script
|
|
main "$@"
|
|
|
|
|
|
# --------------------------------------------------------------------------------
|
|
#
|
|
# CHANGELOG
|
|
# =========
|
|
#
|
|
# Version 0.1.1:
|
|
#
|
|
# - Use "view_in_terminal" to view a password file if an executable with that
|
|
# name exists in the PATH.
|
|
#
|
|
# Version 0.1.0:
|
|
#
|
|
# - First version
|
|
# - Built-in help page
|
|
# - List password files, copy password, copy username, view file in terminal
|
|
#
|
|
# --------------------------------------------------------------------------------
|