#!/bin/bash # passrofi -- Rofi script for pass (passwordstore.org) # Version: 0.1.0 # 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 ) } # Run script main "$@" # -------------------------------------------------------------------------------- # # CHANGELOG # ========= # # Version 0.1.0: # # - First version # - Built-in help page # - List password files, copy password, copy username, view file in terminal # # --------------------------------------------------------------------------------