Skip to content

Instantly share code, notes, and snippets.

@zx0r
Last active January 30, 2025 20:51
Show Gist options
  • Save zx0r/843298b67cd91a0835dcf36aada529d5 to your computer and use it in GitHub Desktop.
Save zx0r/843298b67cd91a0835dcf36aada529d5 to your computer and use it in GitHub Desktop.
GnuPG (GPG) for macOS
#!/usr/bin/env bash
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# :::::::: :::: ::: ::: ::: ::::::::: ::::::::
# :+: :+: :+:+: :+: :+: :+: :+: :+: :+: :+:
# +:+ :+:+:+ +:+ +:+ +:+ +:+ +:+ +:+
# :#: +#+ +:+ +#+ +#+ +:+ +#++:++#+ :#:
# +#+ #+#+# +#+ +#+#+# +#+ +#+ +#+ +#+ #+#+#
# #+# #+# #+# #+#+# #+# #+# #+# #+# #+#
# ######## ### #### ######## ### ########
# Copyright (c) 2025 zx0r. All rights reserved.
# Script: setup-gpg-git-macos.sh
# Author: zx0r
# License: MIT License
# Contact Info: https:#github.com/zx0r
# Version: 1.0
# Date: 2025-01-20
# Description: This script installs and configures GnuPG (GPG) for macOS,
# sets up Git to use GPG for signing commits and ensures
# security settings are applied.It also auto-detects the user
# shell, configures GUI App to use GPG for commits and setup
# launch agent for gpg-agent
# “Stay Hungry, Stay Foolish.”
# - Steve Jobs  Apple
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Enable strict handling
set -euo pipefail
IFS=$'\n\t'
# Regular Colors
BLACK='\033[0;30m' # Black
RED='\033[0;31m' # Red
GREEN='\033[0;32m' # Green
CYAN='\033[0;33m' # CYAN
BLUE='\033[1;34m' # Blue
CYAN='\033[1;34m' # Blue
YELLOW='\033[1;33m' # Yellow
PURPLE='\033[0;35m' # Purple
CYAN='\033[1;36m' # Cyan
WHITE='\033[0;37m' # White
BWHITE='\033[1;37m' # Bold White
NC='\033[0m' # Text Rese# Function to check if a package is installed
# Function to print a step message
print_step() {
local step_name="$1"
echo -e "${BLUE} ➜ ${step_name}${NC}"
}
# Print colorful messages
print_message() {
echo -e "${CYAN}💭 $1${NC}"
}
# Print Success message
print_success() {
echo -e "\n${GREEN}✅ $1${NC}\n"
}
# Print Warnining message
print_warn() {
echo -e "${YELLOW}[Warn] $1${NC}"
}
# Print Error message and exit
print_error() {
echo -e "${RED}❗️$1${NC}"
exit 1
}
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Main ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Main function to orchestrate the setup process
main() {
# First Phase - Setup
initialize_installation # Start
install_dependencies # Install GPG and related tools
detect_shell # Detect the user's shell
create_gpg_dir # Create $HOME/.gnupg
generate_gpg_key # Generate a new GPG key (if needed)
configure_gnupghome # Configure gpg.conf gpg-agent.conf
# Second Phase - Configuration
configure_vscode_gpg # Configure VSCode/VSCodium to use GPG for commits
configure_gui_git_signing # Configure Git to use GPG for signing commits
configure_gpg_agent_launch_agent # Set up gpg-agent as a launch agent
apply_security_settings # Apply additional security settings
configure_ssh_signing # (Option) SSH Key Signing
# configure_smime_signing # (Option) S/MIME Signing using smimesign
configure_git_gpg # Configure Git commit signing
# Third Phase - Verification
ptint_hints # Displaying hints
#export_gpg_key # Export GPG Key
verify_setup # Functional check
complete_installation # End
# Reload Shell
reload_shell # Reload Shell
}
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━ First Phase - Setup ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
initialize_installation() {
print_category "Initialization"
print_step "Starting installation process..."
}
print_category() {
local category_name="$1"
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━${NC} ${PURPLE}${category_name}${NC} ━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
# step_counter=1 # Reset step counter for each new category
}
is_installed() {
local package_name=$1
if command -v "$package_name" &>/dev/null; then
return 0 # Package is installed
else
return 1 # Package is not installed
fi
}
# Install required packages
install_dependencies() {
print_step "Installing dependencies..."
# Check if Homebrew is installed, and install it if not
if ! command -v brew &>/dev/null; then
print_step "Homebrew not found. Installing Homebrew..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
else
print_success "Homebrew is already installed."
fi
# Install gnupg and pinentry-mac if not already installed
if ! is_installed gpg || ! is_installed pinentry-mac || ! is_installed smimesign; then
print_step "Installing gnupg and pinentry-mac and smimesign..."
brew install gnupg pinentry-mac smimesign
else
print_step "Update Homebrew and upgrade existing packages"
brew doctor && brew update && brew upgrade && brew cleanup && brew doctor
fi
}
# Function to detect the user's shell
detect_shell() {
shell_name=$(basename "$SHELL")
# Determine the shell configuration file
case "$shell_name" in
bash)
shell_config="$HOME/.bashrc"
;;
zsh)
shell_config="$HOME/.zshrc"
;;
fish)
shell_config="$HOME/.config/fish/config.fish"
;;
*)
print_warn "Unsupported shell: $shell_name. Please manually configure your shell."
exit 1
;;
esac
print_step "Detected shell: ${GREEN}$shell_name{NC}"
print_step "Using configuration file: ${GREEN}$shell_config${NC}"
export SHELL_NAME="$shell_name"
export SHELL_CONFIG="$shell_config"
}
# Configure GPG
create_gpg_dir() {
local dir="$HOME/.gnupg"
# Check if the directory exists; if not, create it
if [[ ! -d "$dir" ]]; then
print_step "Creating GnuPG directory: ${GREEN}$dir${NC}"
# Create GPG config directory
mkdir -p "$dir" || print_error "Failed to create directory: $dir"
# Set proper permissions
chmod 700 $HOME/.gnupg
fi
}
# Function to generate a new GPG key
generate_gpg_key() {
print_step "Generating a new GPG key..."
# Collect user information securely
read -rp "Enter Your Name for GPG key: " NAME
read -rp "Enter Your Email for GPG key: " EMAIL
read -rsp "[Secure Mode] Enter passphrase for GPG key: " PASSPHRASE
echo
# Ensure clean GPG agent state
gpgconf --kill gpg-agent
gpg-agent --daemon
# Generate key with secure parameters
# Name-Real: $(git config user.name)
# Name-Email: $(git config user.email)
# Name-Comment: GITHUB-KEY
gpg --batch --generate-key <<EOF
Key-Type: RSA
Key-Length: 4096
Subkey-Type: RSA
Subkey-Length: 4096
Name-Real: $NAME
Name-Email: $EMAIL
Passphrase: $PASSPHRASE
Expire-Date: 0
%commit
EOF
GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | tail -1 | awk '{print $2}' | cut -d'/' -f2)
print_success "GPG key generated successfully with passphrase protection"
print_step "GPG key details:"
print_step "• Name: ${GREEN}$NAME${NC}"
print_step "• Email: ${GREEN}$EMAIL${NC}"
print_step "• KEY ID: ${GREEN}${GPG_KEY_ID}{NC}"
}
configure_gnupghome() {
dir="$HOME/.gnupg"
download_url="https://gist.githubusercontent.com/zx0r/843298b67cd91a0835dcf36aada529d5/raw/15a8954620cad5c9bd066237bf210ba475da3014"
# Set GPG to use the TTY for passphrase prompts
export GPG_TTY=$(tty)
export GPG_DIR="$HOME/.gnupg"
# Add GPG_TTY to the user's shell configuration file
if [[ "$SHELL_NAME" == "zsh" ]]; then
echo 'export GPG_TTY=$(tty)' >>"$SHELL_CONFIG"
echo "export GNUPGHOME=$GPG_DIR" >>"$SHELL_CONFIG"
elif [[ "$SHELL_NAME" == "bash" ]]; then
echo 'export GPG_TTY=$(tty)' >>"$SHELL_CONFIG"
echo "export GNUPGHOME=$GPG_DIR" >>"$SHELL_CONFIG"
elif [[ "$SHELL_NAME" == "fish" ]]; then
echo 'set -gx GPG_TTY $(tty)' >>"$SHELL_CONFIG"
echo "set -gx GNUPGHOME $GPG_DIR" >>"$SHELL_CONFIG"
else
print_warn "Unsupported shell. Please manually add 'export GPG_TTY=\$(tty)' to your shell configuration file."
fi
# Configure GPG to use pinentry-mac
curl -fOsSL --output-dir $dir $download_url/gpg-agent.conf
curl -fOsSL --output-dir $dir $download_url/gpg.conf
# To send the keys to the OpenPgp keyserver:
gpg --keyserver keys.openpgp.org --send-key $(gpg --list-secret-keys --with-keygrip --with-colons | grep '^sec:' | awk -F: '{print $5}')
# Get current GPG key ID
KEY_ID=$(gpg --list-secret-keys --keyid-format=long | grep sec | head -1 | cut -d'/' -f2 | cut -d' ' -f1)
# Set it as `default-key` in ~/.gnupg/gpg.conf
sed -i '' "s/\(default-key \)[^ ]*/\1$KEY_ID/" ~/.gnupg/gpg.conf
print_success "Download files ${BLUE}$dir/gpg-agent.conf and $dir/gpg.conf${NC} completed${NC}"
}
# Function to configure Git signing in GUI applications (e.g., Git Tower, GitHub Desktop)
configure_gui_git_signing() {
print_step "Configuring Git signing for GUI applications..."
# Get the GPG key ID
GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | tail -1 | awk '{print $2}' | cut -d'/' -f2)
# Configure Git Tower
if [[ -d "/Applications/Git Tower.app" ]]; then
echo "✓ Configuring Git Tower signing..."
cat >~/.gitconfig.tower <<EOF
[commit]
gpgsign = true
signingkey = $GPG_KEY_ID
EOF
sleep 1
git config --global include.path ~/.gitconfig.tower
fi
# Configure GitHub Desktop
if [[ -d "/Applications/GitHub Desktop.app" ]]; then
print_success "Configuring GitHub Desktop signing..."
cat >~/.gitconfig.github <<EOF
[commit]
gpgsign = true
signingkey = $GPG_KEY_ID
EOF
sleep 1
git config --global include.path ~/.gitconfig.github
fi
print_success "Git signing configured for GUI applications"
}
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━ Second Phase - Configuration ━━━━━━━━━━━━━━━━━━━━━━━━━━
configure_vscode_gpg() {
local extensions=(
"wdhongtw.gpg-indicator"
"jheilingbrunner.vscode-gnupg-tool"
)
local settings='{"git.enableCommitSigning": true}'
if command -v code &>/dev/null; then
print_step "Configuring VSCode for GPG commit signing..."
for ext in "${extensions[@]}"; do
code --force --install-extension "$ext"
done
# code --wait --reuse-window --settings-json "$settings"
print_step "VSCode GPG signing configuration complete"
elif command -v codium &>/dev/null; then
print_step "Configuring VSCodium for GPG commit signing..."
for ext in "${extensions[@]}"; do
codium --force --install-extension "$ext"
done
# codium --wait --reuse-window --settings-json "$settings"
print_success "VSCodium GPG signing configuration complete"
else
print_warn "✗ No compatible editor found (VSCode/VSCodium)"
return 1
fi
}
configure_gpg_agent_launch_agent() {
echo "Setting up gpg-agent as a launch agent..."
# Define paths with more descriptive names
readonly LOCAL_BIN_DIR="$HOME/.local/bin"
readonly GPG_START_SCRIPT="$LOCAL_BIN_DIR/start-gpg-agent.sh"
readonly LAUNCH_AGENTS_DIR="$HOME/Library/LaunchAgents"
readonly GPG_AGENT_PLIST="$LAUNCH_AGENTS_DIR/org.gnupg.gpg-agent.plist"
# Source URLs
readonly PLIST_URL="https://gist.githubusercontent.com/zx0r/843298b67cd91a0835dcf36aada529d5/raw/6673f8ee76898aa8f8f59cfdbfb14728481b1276/org.gnupg.gpg-agent.plist"
readonly SCRIPT_URL="https://gist.githubusercontent.com/zx0r/843298b67cd91a0835dcf36aada529d5/raw/6673f8ee76898aa8f8f59cfdbfb14728481b1276/startup-gpg-agent.sh"
# Create required directories
for dir in "$LOCAL_BIN_DIR" "$LAUNCH_AGENTS_DIR"; do
if [[ ! -d "$dir" ]]; then
mkdir -p "$dir" || {
echo "Failed to create directory: $dir"
return 1
}
echo "Created directory: $dir"
fi
done
# Download files with error checking
if ! curl -fsSL "$PLIST_URL" -o "$GPG_AGENT_PLIST"; then
print_warn "Failed to download plist file"
return 1
fi
if ! curl -fsSL "$SCRIPT_URL" -o "$GPG_START_SCRIPT"; then
print_warn "Failed to download start script"
return 1
fi
# Set correct permissions
chmod +x "$GPG_START_SCRIPT"
# Load the launch agent
launchctl load -w "$GPG_AGENT_PLIST"
print_success "GPG agent launch agent setup complete"
print_step "Installed files:"
print_step "• Launch Agent: ${GREEN}$GPG_AGENT_PLIST${NC}"
print_step "• Start Script: ${GREEN}$GPG_START_SCRIPT${NC}"
}
# Function to apply additional security settings
apply_security_settings() {
echo "Applying additional security settings..."
# Start GPG agent if not running
if ! gpg-connect-agent /bye >/dev/null 2>&1; then
echo "Starting GPG agent..."
gpg-agent --daemon
fi
# Set recommended security configurations
gpg-connect-agent "scd serialno" /bye >/dev/null 2>&1
gpg-connect-agent "learn --force" /bye >/dev/null 2>&1
print_success "Security settings applied successfully."
}
configure_ssh_signing() {
# Define constants
readonly SSH_DIR="$HOME/.ssh"
readonly SSH_KEY="$SSH_DIR/id_ed25519"
GPG_DIR="$HOME/.gnupg"
SSHCONTROL="$GPG_DIR/sshcontrol"
KEYGRIPS="$(gpg --list-keys --with-keygrip | awk '/^sub/{p=1;next} /Keygrip/{if(p){print $3;p=0}}')"
# for Fish Shell
FISH_PATH="$HOME/.config/fish/functions"
SSH_AGENT_FISH_URL="https://gist.githubusercontent.com/zx0r/843298b67cd91a0835dcf36aada529d5/raw/d59f1f8ca738241c6cb34da8055f417cee73dd86/ssh_agent.fish"
GPG_SSH_FISH_URL="https://gist.githubusercontent.com/zx0r/843298b67cd91a0835dcf36aada529d5/raw/d59f1f8ca738241c6cb34da8055f417cee73dd86/gpg_ssh_agent.fish"
SSHCONTROL_URL="https://gist.githubusercontent.com/zx0r/843298b67cd91a0835dcf36aada529d5/raw/deaa0d4c123baad306c0b3e8e78289e985587cd6/sshcontrol"
SSH_CONFIG="https://gist.githubusercontent.com/zx0r/843298b67cd91a0835dcf36aada529d5/raw/5f946ee0bc44f0fe122f19e4461ef07a8e24e3cc/config"
# Create SSH directory with proper permissions
if [[ ! -d "$SSH_DIR" ]]; then
mkdir -p "$SSH_DIR"
chmod 700 "$SSH_DIR"
fi
# Generate SSH key if needed
if [[ ! -f "$SSH_KEY" ]]; then
ssh-keygen -t ed25519 -C "git-signing-key" -f "$SSH_KEY" -N ""
chmod 600 "$SSH_KEY"
chmod 644 "${SSH_KEY}.pub"
fi
# Generate SSH key if needed
if [[ ! -f "$SSH_DIR/config" ]]; then
curl -fsSL "$SSH_CONFIG" -o "$SSH_DIR/config" || print_warn "Failed to download $SSH_DIR/config"
chmod 600 "$SSH_DIR/config"
else
mv "$SSH_DIR/config" "$SSH_DIR/config.bak"
fi
# Add SSH key with keychain integration
ssh-add --apple-use-keychain $SSH_DIR/id_ed25519
# Configure Git signing
git config --global gpg.format ssh
git config --global user.signingkey "$(cat ${SSH_KEY}.pub)"
git config --global commit.gpgsign true
# Setup GPG directory and sshcontrol
mkdir -p "$GPG_DIR"
chmod 700 "$GPG_DIR"
# Extract and add keygrips
curl -fsSL "$SSHCONTROL_URL" -o "$GPG_DIR/sshcontrol" || print_warn "Failed to download $GPG_DIR/sshcontrol"
# Add keygrips to sshcontrol
if [[ -n "$KEYGRIPS" ]]; then
echo "$KEYGRIPS" >>"$SSHCONTROL"
chmod 600 "$SSHCONTROL"
print_success "SSH control configured with keygrips"
fi
# Download ssh_agent.fish
if [[ "$SHELL_NAME" == "fish" ]]; then
curl -fsSL "$SSH_AGENT_FISH_URL" -o "$FISH_PATH/ssh_agent.fish" || print_warn "Failed to download $FISH_PATH/ssh_agent.fish"
curl -fsSL "$GPG_SSH_FISH_URL" -o "$FISH_PATH/gpg_ssh_agent.fish" || print_warn "Failed to download $FISH_PATH/gpg_ssh_agent.fish"
print_step "Download competed: ${GREEN}$FISH_PATH/ssh_agent.fish and $FISH_PATH/gpg_ssh_agent.fish${NC}"
fi
print_success "SSH signing setup complete!"
print_step "Public key: ${BLUE}$(cat ${SSH_KEY}.pub)${NC}"
}
# S/MIME Signing using smimesign
configure_smime_signing() {
# Configure Git to use smimesign
git config --global gpg.x509.program smimesign
git config --global gpg.format x509
git config --global commit.gpgsign true
print_success "S/MIME Signing using smimesign configured"
}
configure_git_gpg() {
# Get the GPG key ID
GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | tail -1 | awk '{print $2}' | cut -d'/' -f2)
# GPG_KEY_ID=$(gpg --list-secret-keys --with-keygrip --with-colons | grep '^sec:' | awk -F: '{print $5}')
# Check and clean existing GPG settings
if git config --list --show-origin | grep -q "commit.gpgsign"; then
git config --global --unset commit.gpgsign
fi
# Configure Git with new GPG settings
git config --global gpg.program $(which gpg)
git config --global user.signingkey "$GPG_KEY_ID"
sleep 1
git config --global log.showSignature "true"
git config --global commit.gpgsign "true"
git config --global tag.gpgSign "true"
# Display success and next steps
print_success "Git configured to use GPG key: $GPG_KEY_ID"
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e " ${GREEN} Your GPG public key for GitHub: ${NC}"
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
# Add to github repository (var 1)
# gpg --armor --export $(gpg --list-secret-keys --with-keygrip --with-colons | grep '^sec:' | awk -F: '{print $5}')
# Add to github repository(var 2)
gpg --armor --export "$GPG_KEY_ID"
}
ptint_hints() {
# Print clear instructions with colors
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e " ${GREEN} Next Steps for GPG Setup ${NC}"
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW}Step 1. 👆 Copy your GPG key 👆${NC}"
echo -e "${YELLOW}• Include both BEGIN and END lines${NC}"
echo -e "${YELLOW}• Ensure no extra spaces are copied${NC}"
echo -e "${YELLOW}Step 2. Add key to GitHub:${NC}"
print_step "${GREEN}https://github.com/settings/gpg/new${NC}"
echo -e "${YELLOW}Step 3. Verify your signed commits:${NC}"
print_step "${GREEN}'git log --show-signature'${NC}"
echo -e "${YELLOW}Step 4. Use commit signature verification:${NC}"
print_step "${GREEN}'git commit -S -m "YOUR_COMMIT_MESSAGE"'${NC}"
echo -e "${YELLOW}Step 5. Learn more about commit signing:${NC}"
print_step "${GREEN}https://docs.github.com/authentication/managing-commit-signature-verification${NC}"
}
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━ Third Phase - Verification ━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Export the GPG Key Materials
export_gpg_key() {
# Get the GPG key ID
GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | tail -1 | awk '{print $2}' | cut -d'/' -f2)
# Set secure export paths
EXPORT_DIR="$HOME/.gnupg/backup"
mkdir -p "$EXPORT_DIR"
# Export both public and private keys with armor
gpg --armor --export "$GPG_KEY_ID" >"$EXPORT_DIR/gpg_public_${GPG_KEY_ID}.asc"
gpg --armor --export-secret-key "$GPG_KEY_ID" >"$EXPORT_DIR/gpg_private_${GPG_KEY_ID}.asc"
print_success "GPG keys exported successfully:"
print_step "• Public key: ${GREEN}$EXPORT_DIR/gpg_public_${GPG_KEY_ID}.asc${NC}"
print_step "• Private key: ${GREEN}$EXPORT_DIR/gpg_private_${GPG_KEY_ID}.asc${NC}"
print_step "$(git config --list --show-origin | grep user.signingkey)"
#print_step "\n$(gpg --list-secret-keys --keyid-format=long)"
}
verify_setup() {
# Print clear instructions with colors
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e " ${GREEN} 🔍 Running system verification... ${NC}"
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
print_step "\n📌 GPG Configuration\n"
gpg --list-secret-keys --keyid-format=long
gpg-connect-agent --quiet /bye
print_success "GPG Agent Status: ✔️"
echo "\n🔑 SSH Configuration\n"
ssh-add -l
print_step "• SSH_AUTH_SOCK: ${GREEN}$SSH_AUTH_SOCK${NC}"
print_success "• SSH Agent Status: ✔️"
print_step "\n✍️ Git Signing Setp\n"
git config --global --list | grep -E 'gpg|signing'
git verify-commit HEAD 2>/dev/null || print_step "No signed commits yet"
print_step "\n🔐 Environment Variables"
print_step "• GPG_TTY: ${GREEN}$GPG_TTY${NC}"
print_step "• GNUPGHOME: ${GREEN}$GNUPGHOME${NC}"
print_step "\n✨ Running signing test...\n"
echo "Final Check" | gpg --clearsign && print_success "GPG signing: ✔️"
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e " ${GREEN} 🎉 Verification complete.Happy Secure Committing 🎉 ${NC}"
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
reload_shell() {
if [[ -f "$SHELL_CONFIG" ]]; then
# print_step "Reloading shell configuration..."
source "$SHELL_CONFIG"
else
print_warn "Error: Shell configuration file not found at $SHELL_CONFIG" >&2
exit 1
fi
}
complete_installation() {
print_category "Completion"
print_success "GnuPG and Git configuration completed successfully 🎉"
}
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ End ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Execute main function only if script is run directly
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main
# $HOME/.ssh/config
# Global Settings
Host *
AddKeysToAgent yes
UseKeychain yes
IdentitiesOnly yes
#IdentityFile ~/.ssh/id_ed25519
# Performance
Compression yes
TCPKeepAlive yes
ServerAliveInterval 60
ServerAliveCountMax 30
# Security
HashKnownHosts yes
PasswordAuthentication no
PubkeyAuthentication yes
IdentitiesOnly yes
# Modern Crypto
KexAlgorithms [email protected],diffie-hellman-group-exchange-sha256
Ciphers [email protected],[email protected]
MACs [email protected],[email protected]
# GitHub
Host github.com
User git
IdentityFile ~/.ssh/github_ed25519
# GitLab
Host gitlab.com
User git
IdentityFile ~/.ssh/gitlab_ed25519
# Custom Server Example
Host dev
HostName dev.example.com
User developer
Port 2222
IdentityFile ~/.ssh/dev_ed25519
# Script: setup-gpg-git-macos.sh
# Author: zx0r
# Date: 2025-01-30
# Description: This script installs and configures GnuPG (GPG) for macOS, sets up Git to use GPG for signing commits,
# and ensures security settings are applied. It also auto-detects the user's shell, configures VSCode/VSCodium
# to use GPG for commits, and sets up a launch agent for gpg-agent.
# Set cache times for normal and SSH keys (in seconds)
default-cache-ttl 3600
max-cache-ttl 7200
default-cache-ttl-ssh 3600
max-cache-ttl-ssh 7200
# Enable SSH support
enable-ssh-support
# Set pinentry program for macOS
pinentry-program /usr/local/bin/pinentry-mac
# Set TTY
ttyname $GPG_TTY
# Write environment file
write-env-file ~/.gnupg/gpg-agent.env
# Keyboard control settings
no-grab
# Allow preset passphrases
allow-preset-passphrase
# Enable loopback for better integration
allow-loopback-pinentry
# Set longer key grace period
ignore-cache-for-signing
# Script: setup-gpg-git-macos.sh
# Author: zx0r
# Date: 2025-01-30
# Description: This script installs and configures GnuPG (GPG) for macOS, sets up Git to use GPG for signing commits,
# and ensures security settings are applied. It also auto-detects the user's shell, configures VSCode/VSCodium
# to use GPG for commits, and sets up a launch agent for gpg-agent.
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Setting defaults │
# └───────────────────────────────────────────────────────────────────────────┘
# Default/trusted key ID to use (helpful with throw-keyids)
# $ gpg --list-secret-keys --with-keygrip --with-colons | grep '^sec:' | awk -F: '{print $5}'
default-key <YOUR-KEY>
# Automatically encrypt replies to encrypted messages to yourself as well
default-recipient-self
# UTF-8 support for compatibility
charset utf-8
# when outputting certificates, view user IDs distinctly from keys:
fixed-list-mode
# Use the GPG agent for passphrase caching
use-agent
# Disable recipient key ID in messages
throw-keyids
# default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed
armor
# include an unambiguous indicator of which key made a signature:
# (see http://thread.gmane.org/gmane.mail.notmuch.general/3721/focus=7234)
# (and http://www.ietf.org/mail-archive/web/openpgp/current/msg00405.html)
# sig-notation [email protected]=%g
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Algorithms & Ciphers │
# └───────────────────────────────────────────────────────────────────────────┘
# SHA512 as digest to sign keys
cert-digest-algo SHA512
# SHA512 as digest for symmetric ops
s2k-digest-algo SHA512
# AES256 as cipher for symmetric ops
s2k-cipher-algo AES256
# Set default key generation algorithm to RSA with 4096-bit length [strong protection]
default-new-key-algo rsa4096
# Use AES256, 192, or 128 as cipher
personal-cipher-preferences AES256 AES192 AES
# Use SHA512, 384, or 256 as digest
personal-digest-preferences SHA512 SHA384 SHA256
# Use ZLIB, BZIP2, ZIP, or no compression
personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed
# Default preferences for new keys
default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Behavior of GnuPG │
# └───────────────────────────────────────────────────────────────────────────┘
# Repair legacy PGP subkey bug during import
import-options repair-pks-subkey-bug
# Remove all signatures from imported keys that are not usable
import-options import-clean
# Remove all non-exportable signatures during export
export-options export-clean
# Display Options
# Show validity of user IDs during key listings
list-options show-uid-validity
# Show validity of user IDs during signature verification
verify-options show-uid-validity
# Show Unix timestamps
fixed-list-mode
# Disable caching of passphrase for symmetrical ops
no-symkey-cache
# Disable banner Interface Options
no-greeting
# No comments on signatures
no-comments
# No version in signatures
no-emit-version
# Show unix timestamps
fixed-list-mode
# Long hexadecimal key format
keyid-format 0xlong
# Display fingerprint
with-fingerprint
# Disable caching of passphrasae for symmetrical encryption
no-symkey-cache
# Disable recipient key ID in messages
# throw-keyids
trust-model always
# Skip time conflict warnings during signature verification
# This is useful when system clock differences might cause issues
ignore-time-conflict
# Ensure cross-certification on signing subkeys
# This enhances security by requiring signatures between primary and subkeys
require-cross-certification
# Commented out: Allow non-standard UIDs during key generation
# Enabling this would allow creation of UIDs without email addresses
# allow-freeform-uid
# Display all keys and their fingerprints
with-fingerprint
# Display key origins and updates
#with-key-origin
# Cross-certify subkeys are present and valid
require-cross-certification
# no-tty
# pinentry-mode loopback
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Keyring & Keyserver │
# └───────────────────────────────────────────────────────────────────────────┘
# Disable the use of the default public and secret keyrings
# This allows explicit specification of which keyrings to use
no-default-keyring
# Keyring Options
# Specify the primary public keyring
# keyring ~/.gnupg/pubring.kbx
# trustdb-name ~/.gnupg/trustdb.gpg
# primary-keyring ~/.gnupg/pubring.kbx
# Automatically retrieve missing keys from keyserver when verifying signatures
# This makes signature verification more seamless by fetching required keys
auto-key-retrieve
# Keyserver Options
# Don't add additional comments in downloaded certificates
keyserver-options no-include-attributes
# Honor the preferred keyserver URL from the key
keyserver-options honor-keyserver-url
# Do not include key signatures from keyserver responses
keyserver-options no-include-revoked
# Include subkeys when downloading keys from keyserver
keyserver-options include-subkeys
# Automatically fetch keys from keyserver when verifying signatures
keyserver-options auto-key-retrieve
# Include revoked keys in search results
keyserver-options include-revoked
# Number of seconds to wait for a keyserver response
keyserver-options timeout=10
# Default keyserver to use
keyserver hkps://keys.openpgp.org
keyserver hkp://zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad.onion
# keyserver hkp://pgp.mit.edu
# keyserver hkp://pool.sks-keyservers.net
# keyserver hkp://keys.gnupg.net
# keyserver hkps://keyserver.ubuntu.com
# keyserver hkps://pgp.mit.edu
# keyserver hkp://keyoxide.org
# keyserver hkp://na.pool.sks-keyservers.net
# keyserver https://sks-keyservers.net/status/
# keyserver hkp://keyserver.ubuntu.com
# keyserver hkp://keybase.io
# keyserver hkp://keyserver.undergrid.net
# keyserver hkp://keyring.debian.org
# keyserver hkp://hkps.pool.sks-keyservers.net
# Define a keygroup named 'purse_keygroup'
# This allows you to refer to multiple keys as a single group
# group purse_keygroup = 0xFF123456 0xABCDEF01 0x12345678
# $HOME/.config/fish/functions/gpg_ssh_agent.fish
function gpg_ssh_agent --description "Start and manage GPG agent"
# Skip for root user
test $USER = root; and return
# Early return if gpgconf is not available
command -q gpgconf; or return 1
# Determine GPG directory
set -l GPG_DIR (test -n "$GNUPGHOME"; and echo $GNUPGHOME; or echo "$HOME/.gnupg")
# Ensure GPG directory exists with secure permissions
if not test -d $GPG_DIR
mkdir -p $GPG_DIR
chmod 700 $GPG_DIR
end
# Set GPG environment file path
set -l GPG_ENV "$GPG_DIR/gpg-agent.env"
# Create or update GPG environment file with secure permissions
if not test -f $GPG_ENV
touch $GPG_ENV
chmod 600 $GPG_ENV
end
# Source existing environment variables if the file is not empty
test -s $GPG_ENV; and source $GPG_ENV ^/dev/null
# Clear SSH_AUTH_SOCK
set -e SSH_AUTH_SOCK
# Set curses for SSH connections if active
test -n "$SSH_CONNECTION"; and set -x PINENTRY_USER_DATA "USE_CURSES=1"
# Set GPG and SSH environment variables
set -gx GPG_TTY (tty)
set -gx GPG_AGENT_INFO (gpgconf --list-dirs agent-socket)
set -gx SSH_AUTH_SOCK (gpgconf --list-dirs agent-ssh-socket)
# Initialize GPG agent
gpg-connect-agent updatestartuptty /bye ^/dev/null
gpgconf --launch gpg-agent
return 0
end
# Call the function to initialize GPG agent at startup
gpg_ssh_agent
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.gnupg.gpg-agent</string>
<key>ProgramArguments</key>
<array>
<string>$HOME/.local/bin/start-gpg-agent.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardErrorPath</key>
<string>~/.gnupg/gpg-agent.log</string>
<key>StandardOutPath</key>
<string>~/.gnupg/gpg-agent.log</string>
</plist>
# Configure your SSH client to automatically add keys to the SSH agent
# $HOME/.ssh/config
# Host *
# AddKeysToAgent yes
# UseKeychain yes # Optional: This is useful on macOS to use the keychain.
# $HOME/.config/fish/functions/ssh_agent.fish
function ssh_agent --description "Start and manage SSH agent"
set -l SSH_ENV "$HOME/.ssh/ssh-agent.env"
# Check if the SSH agent environment file exists
if test -f $SSH_ENV; and test -z "$SSH_AGENT_PID"
source $SSH_ENV >/dev/null
end
# If the SSH agent is not running, start it
if test -z "$SSH_AGENT_PID"
eval (ssh-agent -c) >$SSH_ENV
chmod 600 $SSH_ENV
echo "SSH agent started."
else
echo "Using existing SSH agent."
end
# Add SSH keys
set -l SSH_KEYS (fd '^id_' $HOME/.ssh --type f --exclude '*.pub')
for KEY in $SSH_KEYS
ssh-add $KEY
end
end
# Automatically start the ssh-agent on shell initialization
ssh_agent
# https://github.com/ivakyb/fish_ssh_agent
#
# function ssh_agent_is_started -d "check if ssh agent is already started"
# if begin
# test -f $SSH_ENV; and test -z "$SSH_AGENT_PID"
# end
# source $SSH_ENV >/dev/null
# end
#
# if test -z "$SSH_AGENT_PID"
# return 1
# end
#
# ps -ef | grep $SSH_AGENT_PID | grep -v grep | grep -q ssh-agent
# return $status
# end
#
# function ssh_agent_start -d "start a new ssh agent"
# ssh-agent -c | sed 's/^echo/#echo/' >$SSH_ENV
# chmod 600 $SSH_ENV
# source $SSH_ENV >/dev/null
# true # suppress errors from setenv, i.e. set -gx
# end
#
# function fish_ssh_agent --description "Start ssh-agent if not started yet, or uses already started ssh-agent."
# if test -z "$SSH_ENV"
# set -xg SSH_ENV $HOME/.ssh/ssh-agent.env
# end
#
# if not ssh_agent_is_started
# ssh_agent_start
# end
# end
# Automatically start the ssh-agent on shell initialization
# fish_ssh_agent
# Using a PGP key for SSH authentication
# List of allowed ssh keys. Only keys present in this file are used
# in the SSH protocol. The ssh-add tool may add new entries to this
# file to enable them; you may also add them manually. Comment
# lines, like this one, as well as empty lines are ignored. Lines do
# have a certain length limit but this is not serious limitation as
# the format of the entries is fixed and checked by gpg-agent. A
# non-comment line starts with optional white spaces, followed by the
# keygrip of the key given as 40 hex digits, optionally followed by a
# caching TTL in seconds, and another optional field for arbitrary
# flags. Prepend the keygrip with an '!' mark to disable it.
# gpg --list-keys --with-keygrip | awk '/^sub/{p=1;next} /Keygrip/{if(p){print $3;p=0}}' >> $HOME/.gnupg/sshcontrol
# FF8852FA7D6ED25CF4169132698BBA5536A68134
#!/usr/bin/env bash
# The configuration will keep your GPG agent running smoothly across all applications!
if [ -f $HOME/.gnupg/.gpg-agent-info ] && [ -n "$(pgrep gpg-agent)" ]; then
source $HOME/.gnupg/.gpg-agent-info
export GPG_AGENT_INFO
else
eval $(gpg-agent --daemon --write-env-file $HOME/.gnupg/.gpg-agent-info)
fi
# This line is important for GUI tools to also find it
launchctl setenv GPG_AGENT_INFO $GPG_AGENT_INFO
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment