Part 3: Unifying NeoMutt and isync Configuration Across macOS and Arch Linux
Now we arrive at the final piece: integrating GPG and pass into your actual email setup. The goal is a single configuration that works identically on both macOS and Arch Linux.
3.1 Understanding the Cross-Platform Challenge
Your original isyncrc had macOS-specific code commented out:
# Works on macOS only
# PassCmd "security find-generic-password -a 'alowree@twineintl.com' -s 'neomutt-twine' -w"
# Works on both macOS and Arch Linux
PassCmd "pass Email/alowree@twineintl.com"The commented line uses macOS Keychain. The uncommented line uses pass - which now works on both platforms because you've set up GPG and pass identically on both systems.
3.2 The Unified ~/.config/isyncrc Configuration
Here's your complete, cross-platform compatible isyncrc using pass:
# twine ========================================================
# 1. Configure the remote server
IMAPAccount twine
Host twineintlcom.securemail.hk
User alowree@twineintl.com
# This now works on BOTH macOS and Arch Linux
PassCmd "pass-work Email/alowree@twineintl.com"
Port 993
TLSType IMAPS
AuthMechs PLAIN
# 2. Configure the remote store
IMAPStore twine-remote
Account twine
# 3. Configure the local mail store
MaildirStore twine-local
Path ~/.maildir/twine/
Inbox ~/.maildir/twine/INBOX
SubFolders Verbatim
# 4. Connect them with a channel
Channel twine
Far :twine-remote:
Near :twine-local:
Patterns *
Sync All
Expunge Both
Create Near
Remove Near
SyncState *
CopyArrivalDate yes
ExpireUnread yes
# ==============================================================Key changes:
- Removed the macOS Keychain line entirely
- Using
pass-work(your alias) or directlypasswith full path - No platform-specific conditionals needed
3.3 Making the pass Command Accessible to isync
isync runs in a minimal environment. You have two options to ensure it can find the pass command:
Option A: Use Full Paths (Most Reliable)
Find where pass is installed on each system:
which pass
# macOS (Homebrew): /opt/homebrew/bin/pass
# Arch Linux: /usr/bin/passThen update your isyncrc:
# On macOS
PassCmd "/opt/homebrew/bin/pass-work Email/alowree@twineintl.com"
# On Arch Linux
PassCmd "/usr/bin/pass-work Email/alowree@twineintl.com"But this breaks cross-platform compatibility. Instead, use a wrapper script.
Option B: Create a Wrapper Script (Recommended)
Create a script that finds pass regardless of platform:
# Create the script
mkdir -p ~/.local/bin
nano ~/.local/bin/pass-wrapperAdd this content:
#!/bin/bash
# Cross-platform pass wrapper for isync/neomutt
# Find pass command location
if [[ -x /opt/homebrew/bin/pass ]]; then
PASS_CMD="/opt/homebrew/bin/pass"
elif [[ -x /usr/bin/pass ]]; then
PASS_CMD="/usr/bin/pass"
else
PASS_CMD="pass"
fi
# Set the password store directory
export PASSWORD_STORE_DIR="$HOME/.password-store-work"
# Execute pass with all arguments
exec $PASS_CMD "$@"Make it executable:
chmod +x ~/.local/bin/pass-wrapperNow in your isyncrc:
PassCmd "$HOME/.local/bin/pass-wrapper Email/alowree@twineintl.com"Option C: Environment-Active Configuration (Simplest)
Instead of a wrapper, ensure ~/.local/bin is in your PATH and create symlinks:
# Add to ~/.zshrc (already should be there)
export PATH="$HOME/.local/bin:$PATH"
# Create cross-platform symlink
mkdir -p ~/.local/bin
ln -sf $(which pass) ~/.local/bin/passThen simply use:
PassCmd "pass Email/alowree@twineintl.com"But remember: This uses the default password store. You need to ensure the correct store is set.
3.4 Setting Default Password Store for Email
Since your email passwords are in ~/.password-store-work/, you have two approaches:
Approach 1: Environment Variable in Shell Config
Add to your ~/.zshrc (both macOS and Arch):
# Default password store for email
export PASSWORD_STORE_DIR="$HOME/.password-store-work"
# Convenience alias
alias pass-work="pass"Caveat: This affects all pass commands in your shell, but isync runs in a non-interactive shell that may not read ~/.zshrc.
Approach 2: Explicit Store in Commands (Most Reliable)
Always specify the store explicitly in your isyncrc:
PassCmd "PASSWORD_STORE_DIR=$HOME/.password-store-work pass Email/alowree@twineintl.com"This is the best approach because:
- No dependency on shell configuration
- Works in cron/systemd contexts
- Clear and explicit
- Works identically on both platforms
3.5 Final, Production-Ready isyncrc
Here's your final, cross-platform isyncrc:
# twine ========================================================
# 1. Configure the remote server
IMAPAccount twine
Host twineintlcom.securemail.hk
User alowree@twineintl.com
# Cross-platform password retrieval
PassCmd "PASSWORD_STORE_DIR=$HOME/.password-store-work pass Email/alowree@twineintl.com"
Port 993
TLSType IMAPS
AuthMechs PLAIN
# 2. Configure the remote store
IMAPStore twine-remote
Account twine
# 3. Configure the local mail store
MaildirStore twine-local
Path ~/.maildir/twine/
Inbox ~/.maildir/twine/INBOX
SubFolders Verbatim
# 4. Connect them with a channel
Channel twine
Far :twine-remote:
Near :twine-local:
Patterns *
Sync All
Expunge Both
Create Near
Remove Near
SyncState *
CopyArrivalDate yes
ExpireUnread yes
# ==============================================================3.6 Configuring NeoMutt to Use Pass
Your ~/.config/neomutt/neomuttrc or ~/.muttrc also needs password access for SMTP authentication.
Basic NeoMutt Configuration
# Account settings
set imap_user = "alowree@twineintl.com"
set smtp_url = "smtp://alowree@twineintl.com@smtp.twineintl.com:587"
set folder = "imaps://twineintlcom.securemail.hk"
set spoolfile = "+INBOX"
set record = "+Sent"
set postponed = "+Drafts"
# Password retrieval using pass
set imap_pass = `PASSWORD_STORE_DIR=$HOME/.password-store-work pass Email/alowree@twineintl.com`
set smtp_pass = `PASSWORD_STORE_DIR=$HOME/.password-store-work pass Email/alowree@twineintl.com`Important: The backticks (`) execute the command and substitute its output into the configuration.
More Elegant NeoMutt Configuration Using Source Files
Create separate account files:
# ~/.config/neomutt/accounts/twine
set imap_user = "alowree@twineintl.com"
set smtp_url = "smtp://alowree@twineintl.com@smtp.twineintl.com:587"
set folder = "imaps://twineintlcom.securemail.hk"
set spoolfile = "+INBOX"
set record = "+Sent"
set postponed = "+Drafts"
set imap_pass = `PASSWORD_STORE_DIR=$HOME/.password-store-work pass Email/alowree@twineintl.com`
set smtp_pass = `PASSWORD_STORE_DIR=$HOME/.password-store-work pass Email/alowree@twineintl.com`Then in your main neomuttrc:
# Source the account
source ~/.config/neomutt/accounts/twine3.7 Handling GPG_TTY for Non-Interactive Environments
When isync or NeoMutt calls pass, they may not have GPG_TTY set. This is critical for pinentry to work.
Fix for isync/systemd/cron
Create a wrapper that sets up the environment:
# ~/.local/bin/pass-with-gpg
#!/bin/bash
export GPG_TTY=$(tty)
export PASSWORD_STORE_DIR="$HOME/.password-store-work"
exec /usr/bin/pass "$@"Or for macOS:
# ~/.local/bin/pass-with-gpg (macOS version)
#!/bin/bash
export GPG_TTY=$(tty)
export PASSWORD_STORE_DIR="$HOME/.password-store-work"
exec /opt/homebrew/bin/pass "$@"Make it cross-platform:
# ~/.local/bin/pass-with-gpg (unified)
#!/bin/bash
export GPG_TTY=$(tty)
export PASSWORD_STORE_DIR="$HOME/.password-store-work"
if [[ -x /opt/homebrew/bin/pass ]]; then
exec /opt/homebrew/bin/pass "$@"
else
exec /usr/bin/pass "$@"
fiUse in isyncrc:
PassCmd "$HOME/.local/bin/pass-with-gpg Email/alowree@twineintl.com"3.8 Automated Testing Script
Create a test script to verify everything works:
# ~/bin/test-email-setup.sh
#!/bin/bash
echo "Testing email password retrieval..."
# Test 1: Direct pass access
echo "Test 1: Direct pass access"
PASSWORD_STORE_DIR="$HOME/.password-store-work" pass Email/alowree@twineintl.com > /dev/null
if [ $? -eq 0 ]; then
echo "✓ Pass can retrieve password"
else
echo "✗ Pass failed to retrieve password"
exit 1
fi
# Test 2: GPG agent is running
echo "Test 2: GPG agent status"
gpg-connect-agent 'getinfo version' /bye > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "✓ GPG agent is running"
else
echo "⚠ GPG agent not running (will start on demand)"
fi
# Test 3: Environment variable propagation
echo "Test 3: Command substitution"
PASS_OUTPUT=$(PASSWORD_STORE_DIR="$HOME/.password-store-work" pass Email/alowree@twineintl.com 2>&1)
if [ $? -eq 0 ]; then
echo "✓ Password retrieval successful"
else
echo "✗ Password retrieval failed: $PASS_OUTPUT"
exit 1
fi
echo "All tests passed! Email setup is ready."Run it:
chmod +x ~/bin/test-email-setup.sh
~/bin/test-email-setup.sh3.9 Synchronizing Configuration Across Both Machines
Now that your configuration is unified, here's how to keep it in sync:
Option 1: Dotfiles Repository (Recommended)
# On either machine
cd ~/dotfiles
mkdir -p .config/isync .config/neomutt
# Copy configurations
cp ~/.config/isyncrc .config/isync/
cp ~/.config/neomutt/neomuttrc .config/neomutt/
cp ~/.config/neomutt/accounts/* .config/neomutt/accounts/
# Git commit and push
git add .config/isync .config/neomutt
git commit -m "Unified email config for cross-platform use"
git push
# On the other machine, pull and symlink
cd ~/dotfiles
git pull
ln -sf ~/dotfiles/.config/isync/isyncrc ~/.config/isyncrc
ln -sf ~/dotfiles/.config/neomutt ~/.config/neomuttOption 2: Syncthing or Dropbox
Use Syncthing (open source) or Dropbox to sync ~/.config/isyncrc and ~/.config/neomutt/ across machines.
Important: Never sync ~/.password-store/ or ~/.gnupg/ through unencrypted cloud storage. Use pass git with a private repository for that.
3.10 Complete Setup Verification Checklist
Run through this checklist on both macOS and Arch Linux:
- GPG key exists and works (
gpg --list-secret-keys) - GPG agent caches passphrase (
gpg-connect-agent 'getinfo version' /bye) passis installed and initialized- Work password store exists (
ls ~/.password-store-work/Email/) - Can retrieve password (
pass-work Email/alowree@twineintl.com) isyncrcusesPASSWORD_STORE_DIRenvironment variable- Can run
mbsync -V twinewithout password prompt - NeoMutt configuration sources correctly
- Can send/receive test email
3.11 Troubleshooting Common Cross-Platform Issues
Issue: "pass: command not found" when isync runs
Solution: Use full path or ensure /usr/local/bin (macOS) or /usr/bin (Linux) is in isync's PATH.
Issue: GPG prompts for passphrase every time
Solution: Check gpg-agent.conf and ensure default-cache-ttl is set. On macOS with pinentry-mac, the macOS keychain may also cache it.
Issue: Different behavior between macOS and Linux
Solution: Create a small script that detects the OS and adjusts accordingly:
#!/bin/bash
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS specific settings
export PATH="/opt/homebrew/bin:$PATH"
else
# Linux specific settings
export PATH="/usr/bin:$PATH"
fi3.12 Final Security Reminders
- Never commit
~/.password-store/to public repositories - even though it's encrypted, it's still a risk - Use different GPG keys for work and personal - you've already set this up correctly
- Backup your GPG private keys - store revocation certificates offline
- Regularly rotate your email passwords - use
pass generateto create new ones - Monitor failed login attempts - check email logs for unusual activity
You're Ready!
Your email setup is now:
- ✅ Unified - Same config on macOS and Arch Linux
- ✅ Secure - GPG-encrypted passwords with proper key separation
- ✅ Scriptable -
passprovides clean, predictable output - ✅ Maintainable - No platform-specific conditionals needed
Next steps:
- Test
mbsync -V twineto sync your email - Launch NeoMutt and verify IMAP/SMTP work
- Set up cron/systemd timers for automatic syncing (if desired)
- Consider setting up
msmtpfor even better SMTP integration
Congratulations! You've successfully migrated from macOS Keychain to a cross-platform GPG+pass solution.
