Skip to content

Instantly share code, notes, and snippets.

@col
Last active November 19, 2025 23:45
Show Gist options
  • Select an option

  • Save col/437a51f84ea8cceffdc849fa136b2450 to your computer and use it in GitHub Desktop.

Select an option

Save col/437a51f84ea8cceffdc849fa136b2450 to your computer and use it in GitHub Desktop.
Load .env values from 1Password

1Password Environment File Parser

A bash script that automatically replaces 1Password references in .env files with their actual values, making it easy to manage secrets securely in your development workflow.

Features

  • 🔐 Securely fetch secrets from 1Password vault
  • 🔄 Replace op:// references with actual values
  • 📝 Preserve comments and formatting in env files
  • 🔍 Dry run mode to preview changes
  • ✅ Verify mode to check all references are valid
  • 💾 Automatic backup creation when overwriting files
  • 🎨 Colored output for better readability
  • 🛡️ Secure file permissions (600) for output files

Prerequisites

  1. 1Password CLI: Install the 1Password command-line tool

    # macOS
    brew install --cask 1password-cli
    
    # Linux (Ubuntu/Debian)
    curl -sS https://downloads.1password.com/linux/keys/1password.asc | \
      sudo gpg --dearmor --output /usr/share/keyrings/1password-archive-keyring.gpg
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/$(dpkg --print-architecture) stable main" | \
      sudo tee /etc/apt/sources.list.d/1password.list
    sudo apt update && sudo apt install 1password-cli
  2. Sign in to 1Password:

    # Sign in to your 1Password account
    eval $(op signin)

Installation

  1. Download the script:

    curl -O https://raw.githubusercontent.com/col/load_env_from_1pass/main/load_env_from_1pass.sh
    chmod +x load_env_from_1pass.sh
  2. (Optional) Move to a directory in your PATH:

    sudo mv load_env_from_1pass.sh /usr/local/bin/load_env_from_1pass

Usage

Basic Usage

# Output to stdout
./load_env_from_1pass.sh .env.template

# Save to a new file
./load_env_from_1pass.sh .env.template .env

# Pipe to another command
./load_env_from_1pass.sh .env.template | grep DATABASE

Command Options

Usage: load_env_from_1pass.sh [OPTIONS] <input.env> [output.env]

Options:
  -h, --help     Show help message
  -d, --dry-run  Show what would be replaced without making changes
  -v, --verify   Verify that all references can be resolved

Examples

  1. Replace references and save to file:

    ./load_env_from_1pass.sh .env.template .env
  2. Dry run to see what will be replaced:

    ./load_env_from_1pass.sh --dry-run .env.template
  3. Verify all references are valid:

    ./parse-1password-env.sh --verify .env.template
  4. Use in a Docker build process:

    ./parse-1password-env.sh .env.template > .env
    docker-compose up

1Password Reference Format

The script recognizes 1Password references in the following format:

op://vault/item/field
op://vault/item/section/field

Examples in .env file:

# Simple reference
DATABASE_PASSWORD="op://Personal/PostgreSQL/password"

# With section
AWS_SECRET="op://DevOps/AWS/credentials/secret_key"

# Without quotes (also supported)
API_KEY=op://Work/API Keys/github_token

# Single quotes work too
SMTP_PASSWORD='op://Email/SMTP Server/password'

How It Works

  1. The script reads your .env file line by line
  2. Identifies values that start with op://
  3. Uses the 1Password CLI to fetch the actual value
  4. Replaces the reference with the real value
  5. Preserves quotes, comments, and formatting
  6. Outputs the result to stdout or a file

Security Considerations

  • File Permissions: Output files are automatically set to 600 (read/write for owner only)
  • Backups: When overwriting existing files, a timestamped backup is created
  • Memory: Secrets are only kept in memory during processing
  • No Logging: The script doesn't log sensitive values
  • Validation: Use --verify mode to ensure all references are valid before deployment

Common Use Cases

Development Environment Setup

# Keep a template in version control
git add .env.template
echo ".env" >> .gitignore

# Generate local .env file
./load_env_from_1pass.sh .env.template .env

Troubleshooting

"1Password CLI (op) is not installed"

  • Install the 1Password CLI following the prerequisites section
  • Ensure op is in your PATH: which op

"You are not signed in to 1Password"

  • Run: eval $(op signin)
  • For automated scripts, use: op signin --raw > ~/.op-session

"Failed to fetch value for: op://..."

  • Verify the reference path is correct: op read "op://vault/item/field"
  • Check you have access to the vault/item
  • Ensure the field name is spelled correctly

Permission Denied

  • Make the script executable: chmod +x load_env_from_1pass.sh
  • Check write permissions for the output directory

Best Practices

  1. Version Control: Commit .env.template files, never .env files
  2. Naming Convention: Use .env.template or .env.example for templates
  3. Documentation: Document required 1Password items in your README
  4. Rotation: Regularly rotate secrets in 1Password

License

MIT License - Feel free to use and modify as needed.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

For issues or questions:

  1. Check the troubleshooting section above
  2. Run with set -x for debug output
  3. Open an issue on GitHub
#!/bin/bash
# Script to parse .env file and replace 1password references with actual values
# Usage: ./load_env_from_1pass.sh [input.env] [output.env]
# If output file is not specified, it will output to stdout
# See: https://gist.github.com/col/437a51f84ea8cceffdc849fa136b2450
set -euo pipefail
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Function to print colored messages
print_error() {
echo -e "${RED}Error: $1${NC}" >&2
}
print_success() {
echo -e "${GREEN}$1${NC}" >&2
}
print_info() {
echo -e "${YELLOW}$1${NC}" >&2
}
# Function to check if 1password CLI is installed
check_op_cli() {
if ! command -v op &> /dev/null; then
print_error "1Password CLI (op) is not installed or not in PATH"
echo "Please install it from: https://developer.1password.com/docs/cli/get-started/" >&2
exit 1
fi
}
# Function to check if user is signed in to 1password
check_op_signin() {
if ! op account get &> /dev/null; then
print_error "You are not signed in to 1Password"
echo "Please run: eval \$(op signin)" >&2
exit 1
fi
}
# Function to fetch value from 1password
fetch_op_value() {
local reference="$1"
local value
# Try to fetch the value from 1password
if value=$(op read "$reference" 2>/dev/null); then
echo "$value"
return 0
else
print_error "Failed to fetch value for: $reference"
return 1
fi
}
# Function to process a single line
process_line() {
local line="$1"
local processed_line="$line"
# Skip empty lines and comments
if [[ -z "$line" ]] || [[ "$line" =~ ^[[:space:]]*# ]]; then
echo "$line"
return
fi
# Check if line contains a key=value pair
if [[ "$line" =~ ^([^=]+)=(.*)$ ]]; then
local key="${BASH_REMATCH[1]}"
local value="${BASH_REMATCH[2]:-}"
local original_value="${value}"
# Remove quotes if present
value="${value#\"}"
value="${value%\"}"
value="${value#\'}"
value="${value%\'}"
# Check if value is a 1password reference (op://...)
if [[ "$value" =~ ^op://.+ ]]; then
print_info "Processing: $key"
if new_value=$(fetch_op_value "$value"); then
# Escape special characters in the value
new_value="${new_value//\\/\\\\}" # Escape backslashes
new_value="${new_value//\"/\\\"}" # Escape double quotes
# Check if the original value had quotes
if [[ "$original_value" =~ ^[\"\'] ]]; then
processed_line="${key}=\"${new_value}\""
else
processed_line="${key}=${new_value}"
fi
print_success " ✓ Replaced $key"
else
print_error " ✗ Failed to replace $key (keeping original)"
# Keep the original line if we can't fetch the value
fi
fi
fi
echo "$processed_line"
}
# Function to process the entire file
process_env_file() {
local input_file="$1"
local output_file="${2:-}"
local temp_file
# Check if input file exists
if [[ ! -f "$input_file" ]]; then
print_error "Input file not found: $input_file"
exit 1
fi
# Create a temporary file for processing
temp_file=$(mktemp)
trap "rm -f $temp_file" EXIT
print_info "Processing file: $input_file"
echo "" >&2
# Process each line of the input file
while IFS= read -r line || [[ -n "$line" ]]; do
process_line "$line" >> "$temp_file"
done < "$input_file"
# Output to file or stdout
if [[ -n "$output_file" ]]; then
# Create backup if output file exists
if [[ -f "$output_file" ]]; then
backup_file="${output_file}.backup.$(date +%Y%m%d_%H%M%S)"
cp "$output_file" "$backup_file"
print_info "Created backup: $backup_file"
fi
mv "$temp_file" "$output_file"
chmod 600 "$output_file" # Set restrictive permissions for security
echo "" >&2
print_success "Successfully wrote to: $output_file"
else
cat "$temp_file"
fi
}
# Function to show usage
show_usage() {
cat << EOF
Usage: $0 [OPTIONS] [input.env] [output.env]
Parse a .env file and replace 1password references (op://...) with their actual values.
Arguments:
input.env Input environment file (default: .env.template)
output.env Output file (default: .env when input is provided, stdout when using defaults)
Options:
-h, --help Show this help message
-d, --dry Dry run - show what would be replaced without making changes
-v, --verify Verify that all references can be resolved
Examples:
# Use defaults (.env.template -> .env)
$0
# Custom input, output to stdout
$0 custom.env.template
# Custom input and output
$0 custom.env.template custom.env
# Verify default template
$0 --verify
# Dry run on default template
$0 --dry
1Password Reference Format:
op://vault/item/field
op://vault/item/section/field
Example: DATABASE_PASSWORD="op://Personal/Database/password"
Prerequisites:
- 1Password CLI (op) must be installed
- You must be signed in: eval \$(op signin)
EOF
}
# Function for dry run mode
dry_run() {
local input_file="$1"
if [[ ! -f "$input_file" ]]; then
print_error "Input file not found: $input_file"
exit 1
fi
print_info "Dry run mode - showing 1password references found:"
echo "" >&2
while IFS= read -r line; do
if [[ "$line" =~ ^([^=]+)=(.*op://.+.*)$ ]]; then
local key="${BASH_REMATCH[1]}"
local value="${BASH_REMATCH[2]:-}"
# Remove quotes
value="${value#\"}"
value="${value%\"}"
value="${value#\'}"
value="${value%\'}"
if [[ "$value" =~ ^op://.+ ]]; then
echo " $key = $value" >&2
fi
fi
done < "$input_file"
}
# Function to verify all references
verify_references() {
local input_file="$1"
local all_valid=true
if [[ ! -f "$input_file" ]]; then
print_error "Input file not found: $input_file"
exit 1
fi
print_info "Verifying 1password references..."
echo "" >&2
while IFS= read -r line; do
if [[ "$line" =~ ^([^=]+)=(.*op://.+.*)$ ]]; then
local key="${BASH_REMATCH[1]}"
local value="${BASH_REMATCH[2]:-}"
# Remove quotes
value="${value#\"}"
value="${value%\"}"
value="${value#\'}"
value="${value%\'}"
if [[ "$value" =~ ^op://.+ ]]; then
if op read "$value" &> /dev/null; then
print_success " ✓ $key: $value"
else
print_error " ✗ $key: $value"
all_valid=false
fi
fi
fi
done < "$input_file"
echo "" >&2
if $all_valid; then
print_success "All references are valid!"
return 0
else
print_error "Some references could not be resolved"
return 1
fi
}
# Main script
main() {
local input_file=".env.template" # Default input file
local output_file=".env" # Default output file
local mode="process"
local use_defaults=true # Track if using defaults
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
exit 0
;;
-d|--dry|--dry-run)
mode="dry"
shift
;;
-v|--verify)
mode="verify"
shift
;;
-*)
print_error "Unknown option: $1"
show_usage
exit 1
;;
*)
if $use_defaults; then
# First positional argument replaces default input file
input_file="$1"
output_file="" # Clear default output if custom input provided
use_defaults=false
elif [[ -z "$output_file" ]]; then
# Second positional argument is output file
output_file="$1"
fi
shift
;;
esac
done
# Check if input file exists (whether default or specified)
if [[ ! -f "$input_file" ]]; then
if $use_defaults; then
print_error "Default input file not found: $input_file"
echo "Please create a .env.template file or specify an input file" >&2
else
print_error "Input file not found: $input_file"
fi
show_usage
exit 1
fi
# Check prerequisites
check_op_cli
check_op_signin
# Execute based on mode
case $mode in
dry)
dry_run "$input_file"
;;
verify)
verify_references "$input_file"
;;
process)
process_env_file "$input_file" "$output_file"
;;
esac
}
# Run main function
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment