Skip to content
0

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:

bash
# 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:

bash
# 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 directly pass with 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:

bash
which pass
# macOS (Homebrew): /opt/homebrew/bin/pass
# Arch Linux: /usr/bin/pass

Then update your isyncrc:

bash
# 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.

Create a script that finds pass regardless of platform:

bash
# Create the script
mkdir -p ~/.local/bin
nano ~/.local/bin/pass-wrapper

Add this content:

bash
#!/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:

bash
chmod +x ~/.local/bin/pass-wrapper

Now in your isyncrc:

bash
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:

bash
# 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/pass

Then simply use:

bash
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):

bash
# 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:

bash
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:

bash
# 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

bash
# 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:

bash
# ~/.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:

bash
# Source the account
source ~/.config/neomutt/accounts/twine

3.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:

bash
# ~/.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:

bash
# ~/.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:

bash
# ~/.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 "$@"
fi

Use in isyncrc:

bash
PassCmd "$HOME/.local/bin/pass-with-gpg Email/alowree@twineintl.com"

3.8 Automated Testing Script

Create a test script to verify everything works:

bash
# ~/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:

bash
chmod +x ~/bin/test-email-setup.sh
~/bin/test-email-setup.sh

3.9 Synchronizing Configuration Across Both Machines

Now that your configuration is unified, here's how to keep it in sync:

bash
# 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/neomutt

Option 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)
  • pass is installed and initialized
  • Work password store exists (ls ~/.password-store-work/Email/)
  • Can retrieve password (pass-work Email/alowree@twineintl.com)
  • isyncrc uses PASSWORD_STORE_DIR environment variable
  • Can run mbsync -V twine without 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:

bash
#!/bin/bash
if [[ "$OSTYPE" == "darwin"* ]]; then
    # macOS specific settings
    export PATH="/opt/homebrew/bin:$PATH"
else
    # Linux specific settings
    export PATH="/usr/bin:$PATH"
fi

3.12 Final Security Reminders

  1. Never commit ~/.password-store/ to public repositories - even though it's encrypted, it's still a risk
  2. Use different GPG keys for work and personal - you've already set this up correctly
  3. Backup your GPG private keys - store revocation certificates offline
  4. Regularly rotate your email passwords - use pass generate to create new ones
  5. 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 - pass provides clean, predictable output
  • Maintainable - No platform-specific conditionals needed

Next steps:

  1. Test mbsync -V twine to sync your email
  2. Launch NeoMutt and verify IMAP/SMTP work
  3. Set up cron/systemd timers for automatic syncing (if desired)
  4. Consider setting up msmtp for even better SMTP integration

Congratulations! You've successfully migrated from macOS Keychain to a cross-platform GPG+pass solution.

最近更新