Skip to content

Instantly share code, notes, and snippets.

@zx0r
Last active January 30, 2025 20:51

Revisions

  1. zx0r revised this gist Jan 30, 2025. 1 changed file with 7 additions and 1 deletion.
    8 changes: 7 additions & 1 deletion a_setup-gpg-git-macos.sh
    Original file line number Diff line number Diff line change
    @@ -265,6 +265,9 @@ configure_gnupghome() {
    # 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)
    @@ -514,9 +517,12 @@ configure_git_gpg() {
    echo -e " ${GREEN} Your GPG public key for GitHub: ${NC}"
    echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"

    gpg --armor --export "$GPG_KEY_ID"
    # 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() {
  2. zx0r revised this gist Jan 30, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion a_setup-gpg-git-macos.sh
    Original file line number Diff line number Diff line change
    @@ -146,7 +146,7 @@ install_dependencies() {
    # 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
    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
  3. zx0r revised this gist Jan 30, 2025. 1 changed file with 626 additions and 0 deletions.
    626 changes: 626 additions & 0 deletions a_setup-gpg-git-macos.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,626 @@
    #!/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
    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

    # 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}"

    gpg --armor --export "$GPG_KEY_ID"
    # gpg --armor --export $(gpg --list-secret-keys --with-keygrip --with-colons | grep '^sec:' | awk -F: '{print $5}')

    }

    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
  4. zx0r revised this gist Jan 30, 2025. 1 changed file with 41 additions and 0 deletions.
    41 changes: 41 additions & 0 deletions config
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,41 @@
    # $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 curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
    Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
    MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com

    # 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
  5. zx0r revised this gist Jan 30, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion sshcontrol
    Original file line number Diff line number Diff line change
    @@ -12,4 +12,4 @@
    # 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
    # FF8852FA7D6ED25CF4169132698BBA5536A68134
  6. zx0r revised this gist Jan 30, 2025. 1 changed file with 72 additions and 0 deletions.
    72 changes: 72 additions & 0 deletions ssh_agent.fish
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,72 @@

    # 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
  7. zx0r revised this gist Jan 30, 2025. 1 changed file with 49 additions and 0 deletions.
    49 changes: 49 additions & 0 deletions gpg_ssh_agent.fish
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,49 @@
    # $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
  8. zx0r revised this gist Jan 30, 2025. 1 changed file with 15 additions and 0 deletions.
    15 changes: 15 additions & 0 deletions sshcontrol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,15 @@
    # 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
  9. zx0r revised this gist Jan 30, 2025. 2 changed files with 31 additions and 0 deletions.
    19 changes: 19 additions & 0 deletions org.gnupg.gpg-agent.plist
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,19 @@
    <?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>
    12 changes: 12 additions & 0 deletions startup-gpg-agent.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,12 @@
    #!/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
  10. zx0r revised this gist Jan 30, 2025. 2 changed files with 9 additions and 5 deletions.
    7 changes: 2 additions & 5 deletions gpg-agent.conf
    Original file line number Diff line number Diff line change
    @@ -1,12 +1,9 @@
    # 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.
    # Author: zx0r
    # Date: 2025-01-30

    # https://github.com/drduh/config/blob/master/gpg-agent.conf
    # https://www.gnupg.org/documentation/manuals/gnupg/Agent-Options.html

    # Set cache times for normal and SSH keys (in seconds)
    default-cache-ttl 3600
    7 changes: 7 additions & 0 deletions gpg.conf
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,10 @@
    # 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 │
    # └───────────────────────────────────────────────────────────────────────────┘
  11. zx0r created this gist Jan 30, 2025.
    39 changes: 39 additions & 0 deletions gpg-agent.conf
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,39 @@
    # Script: setup-gpg-git-macos.sh
    # 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.
    # Author: zx0r
    # Date: 2025-01-30

    # https://github.com/drduh/config/blob/master/gpg-agent.conf
    # https://www.gnupg.org/documentation/manuals/gnupg/Agent-Options.html

    # 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
    195 changes: 195 additions & 0 deletions gpg.conf
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,195 @@
    # ┌───────────────────────────────────────────────────────────────────────────┐
    # │ 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 issuer-fpr@notations.openpgp.fifthhorseman.net=%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