#!/usr/bin/env bash # pass extension for managing .env files VERSION="0.1.0" export PASSWORD_STORE_DIR="${PASSWORD_STORE_DIR:-$HOME/.password-store}" # Robust gpg-agent environment setup if [ -n "${GPG_AGENT_INFO}" ]; then # gpg-agent is already running and GPG_AGENT_INFO is set export GPG_AGENT_INFO elif pgrep -x gpg-agent >/dev/null; then # gpg-agent is running but GPG_AGENT_INFO is not set, try to find it if [ -f "${HOME}/.gnupg/gpg-agent-info" ]; then . "${HOME}/.gnupg/gpg-agent-info" export GPG_AGENT_INFO fi else # gpg-agent is not running, start it eval $(gpg-agent --daemon) fi # Ensure GPG_TTY is set for non-interactive shells export GPG_TTY=$(tty) # Helper functions die() { echo "Error: $*" >&2 exit 1 } yesno() { local answer read -r -p "$1 [y/N] " answer [[ "$answer" =~ [Yy] ]] } # Import .env file into pass cmd_import() { local env_file="${1:-.env}" [[ -f "$env_file" ]] || die "Environment file not found: $env_file" local project_name project_name=$(basename "$PWD") echo "Importing environment variables for project: $project_name" while IFS= read -r line || [[ -n "$line" ]]; do # Trim leading whitespace from the whole line first line="${line#"${line%%[![:space:]]*}"}" # Skip empty lines and comment-only lines if [[ -z "$line" ]] || [[ "$line" =~ ^# ]]; then continue fi local comment="" local key_value_part="$line" # Check for inline comment if [[ "$line" == *"#"* ]]; then key_value_part="${line%%#*}" comment="${line#*#}" fi # Split on the first '=' local key="${key_value_part%%=*}" local value="${key_value_part#*=}" # Trim whitespace from key and value key="${key#"${key%%[![:space:]]*}"}" key="${key%"${key##*[![:space:]]}"}" value="${value#"${value%%[![:space:]]*}"}" value="${value%"${value##*[![:space:]]}"}" # Check for *file* tag in comment if [[ "$comment" == *"*file*"* ]]; then # The value is a file path. Let's remove quotes around it if any. if [[ "$value" =~ ^'(.*)'$ ]]; then value="${BASH_REMATCH[1]}" elif [[ "$value" =~ ^"(.*)"$ ]]; then value="${BASH_REMATCH[1]}" fi local file_path="$value" if [[ ! -f "$file_path" ]]; then local env_dir env_dir=$(dirname "$env_file") if [[ -f "$env_dir/$file_path" ]]; then file_path="$env_dir/$file_path" else echo "Warning: File not found for $key: $value. Skipping." >&2 continue fi fi echo "Reading value for $key from file: $file_path" local stored_path local project_root project_root=$(realpath "$PWD") local abs_file_path abs_file_path=$(realpath "$file_path") # Check if the file is within the project directory if [[ "$abs_file_path" == "$project_root"* ]]; then # Store the relative path stored_path="${abs_file_path#$project_root/}" else # Store only the filename, preserving full name stored_path="${abs_file_path##*/}" fi echo "Debug: Storing path: '$stored_path'" >&2 local file_content file_content=$(<"$file_path") value="# dotenv-file-path: $stored_path $file_content" else # It's a regular value, remove quotes if they are there. if [[ "$value" =~ ^'(.*)'$ ]]; then value="${BASH_REMATCH[1]}" elif [[ "$value" =~ ^"(.*)"$ ]]; then value="${BASH_REMATCH[1]}" fi fi local store_path="dotenv/$project_name/$key" echo "Importing $key to $store_path" printf "%s" "$value" | pass insert --multiline "$store_path" >/dev/null || die "Failed to insert $key" done < "$env_file" echo "Import complete." } # Export .env file from pass cmd_export() { local project_name="${1:-$(basename "$PWD")}" local env_file=".env" echo "Exporting environment variables for project: $project_name" local project_path="dotenv/$project_name" local project_store_path="$PASSWORD_STORE_DIR/$project_path" if [[ ! -d "$project_store_path" ]]; then die "No environment variables found for project: $project_name" fi if [[ -f "$env_file" ]] && ! yesno "Overwrite existing $env_file?"; then die "Export aborted." fi # Clear the file > "$env_file" # Use find to get a reliable, machine-readable list of secret files. find "$project_store_path" -type f -name "*.gpg" | while read -r gpg_file; do # Derive the secret name from the gpg file path local entry entry="${gpg_file#$PASSWORD_STORE_DIR/}" entry="${entry%.gpg}" local key key=$(basename "$entry") local full_content full_content=$(/usr/bin/pass show "$entry") # Extract the header line and then the stored_path using sed local header_line header_line=$(echo "$full_content" | head -n 1) if [[ "$header_line" =~ ^#\ dotenv-file-path:\ (.*) ]]; then local stored_path="${BASH_REMATCH[1]}" local file_content file_content=$(echo "$full_content" | tail -n +2) local new_filepath="$stored_path" # Ensure the directory exists local new_file_dir new_file_dir=$(dirname "$new_filepath") if [[ ! -d "$new_file_dir" ]]; then mkdir -p "$new_file_dir" fi echo "Exporting $key to file: $new_filepath" printf "%s" "$file_content" > "$new_filepath" echo "$key=$new_filepath # *file*" >> "$env_file" else echo "Exporting $key" echo "$key=$full_content" >> "$env_file" fi done echo "Export complete. See $env_file" } # Show help cmd_help() { cat <<-_EOF Usage: pass dotenv import|export [project_name] Commands: import - Import a .env file into the password store. export - Export a .env file from the password store. _EOF } # Main command handler case "$1" in import) shift cmd_import "$@" ;; export) shift cmd_export "$@" ;; -h|--help|help) cmd_help ;; *) die 'Usage: pass dotenv import|export [project_name]' ;; esac