Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions .claude/tasks/2025-07-07-bash-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

**Task**: Migrate dotfiles installation scripts from custom zsh test framework to industry-standard bash + shellcheck + bats
**Approach**: Complete 3-phase migration for better tooling, maintainability, and reliability
**Current**: Phase 1 complete (PR #13 merged), critical setup.zsh bug fixed (PR #14 merged), ready for SSH migration
**Current**: Phase 1 continuing with SSH migration (PR #15 in draft), 2 of 3 core scripts migrated

## Migration Strategy

### Phase 1: Demonstrate Complete Migration Pattern (COMPLETED)
**Goal**: Create one complete bash + bats example to establish the migration pattern
### Phase 1: Core Script Migration (IN PROGRESS - 2/3 Complete)
**Goal**: Migrate core installation scripts (GitHub, SSH, Homebrew) to establish patterns

#### βœ… PR #13: GitHub Installation Testing (MERGED)
- **Files Created**:
Expand Down Expand Up @@ -151,10 +151,22 @@ test/install/test-{component}-installation.bats # Integration tests
- Feature parity verification required
- Complete behavior bundle (no dead code)

## Current Work

### πŸ”„ PR #15: SSH Installation Testing (Draft)
- **Files Created**:
- `lib/ssh-utils.bash` - SSH utilities with comprehensive functionality
- `bin/install/ssh.bash` - Bash replacement for ssh.zsh
- `test/install/test-ssh-utils.bats` - 14 utility function tests
- `test/install/test-ssh-installation.bats` - 7 integration tests
- **Test Results**: All 21 tests passing, shellcheck compliance verified
- **Improvements**: Better macOS version detection for ssh-add keychain flag
- **Status**: Draft PR created, ready for review

## Next Steps

1. **Continue Phase 1**: SSH installation script migration to bash + bats
2. **Update CI Workflow**: Migrate from custom zsh runner to bats
1. **Update CI Workflow**: Migrate from custom zsh runner to bats (after PR #15)
2. **Continue Phase 1**: Homebrew installation migration to complete core scripts
3. **Plan Phase 2**: Define systematic migration order for remaining scripts
4. **Document Migration Pattern**: Create guide for future script conversions
5. **Performance Testing**: Compare bash vs zsh test execution times
Expand Down
93 changes: 93 additions & 0 deletions bin/install/ssh.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env bash

# SSH installation script
# Handles SSH key generation, configuration, and agent setup
#
# This script:
# 1. Generates SSH keys if not present
# 2. Creates/updates SSH config file
# 3. Adds keys to ssh-agent
# 4. Adds keys to macOS Keychain

set -euo pipefail

# Configuration
DOTFILES="${DOTFILES:-$HOME/Repos/ooloth/dotfiles}"

# Load utilities
# shellcheck source=lib/ssh-utils.bash
source "$DOTFILES/lib/ssh-utils.bash"

main() {
echo "πŸ”‘ Installing SSH key pair"
echo ""

# Check for existing SSH keys
echo "πŸ” Checking for existing SSH keys"

if detect_ssh_keys; then
# Keys found, exit early
return 0
fi

# Generate new SSH keys
echo ""
echo "✨ Generating a new 2048-bit RSA SSH public/private key pair."

if generate_ssh_keys; then
echo ""
echo "βœ… SSH key pair generated successfully."
else
echo ""
echo "❌ Failed to generate SSH key pair."
return 1
fi

# Create/update SSH config file
echo ""
echo "πŸ“„ Creating SSH config file"

local ssh_config="$HOME/.ssh/config"

if [[ -f "$ssh_config" ]]; then
echo ""
echo "βœ… SSH config file found. Checking contents..."

if ssh_config_has_required_settings "$ssh_config"; then
echo ""
echo "βœ… SSH config file contains all the expected settings."
else
echo ""
echo "❌ SSH config file does not contain all the expected settings. Updating..."
create_ssh_config "$ssh_config"
echo ""
echo "βœ… SSH config file updated."
fi
else
echo "SSH config file does not exist. Creating..."
create_ssh_config "$ssh_config"
echo ""
echo "βœ… SSH config file created."
fi

# Add key to SSH agent and Keychain
echo ""
echo "πŸ”‘ Adding SSH key pair to ssh-agent and Keychain"

if add_ssh_key_to_agent; then
echo ""
echo "βœ… SSH key added successfully."
else
echo ""
echo "❌ Failed to add SSH key to agent."
return 1
fi

echo ""
echo "πŸš€ Done configuring your SSH key pair."
}

# Run main function if script is executed directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
104 changes: 104 additions & 0 deletions lib/ssh-utils.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env bash

# SSH utility functions for installation scripts
# Provides reusable functionality for detecting and working with SSH keys

set -euo pipefail

# Get SSH key paths (centralized configuration)
get_ssh_key_paths() {
SSH_DIR="${HOME}/.ssh"
SSH_PRIVATE_KEY="${SSH_DIR}/id_rsa"
SSH_PUBLIC_KEY="${SSH_DIR}/id_rsa.pub"
}

# Check if SSH keys exist on the system (no output)
# Returns: 0 if both private and public keys exist, 1 if not
ssh_keys_exist() {
get_ssh_key_paths
[[ -s "$SSH_PRIVATE_KEY" && -s "$SSH_PUBLIC_KEY" ]]
}

# Detect if SSH keys exist on the system (with output)
# Prints informative messages and returns appropriate exit codes
# Returns: 0 if both private and public keys exist, 1 if not
detect_ssh_keys() {
if ssh_keys_exist; then
echo "βœ… SSH key pair found."
return 0
else
echo "πŸ‘Ž No SSH key pair found."
return 1
fi
}

# Create SSH config file with appropriate settings
# Arguments: $1 - SSH config file path
create_ssh_config() {
local ssh_config="$1"
local ssh_dir
ssh_dir=$(dirname "$ssh_config")

get_ssh_key_paths

mkdir -p "$ssh_dir"

cat > "$ssh_config" << EOF
Host *
AddKeysToAgent yes
UseKeychain yes
IdentityFile $SSH_PRIVATE_KEY
EOF
}

# Check if SSH config has all required settings
# Arguments: $1 - SSH config file path
# Returns: 0 if all settings present, 1 if any missing
ssh_config_has_required_settings() {
local ssh_config="$1"

if [[ ! -f "$ssh_config" ]]; then
return 1
fi

get_ssh_key_paths

# Check each required setting
grep -Fxq "Host *" "$ssh_config" && \
grep -Fxq " AddKeysToAgent yes" "$ssh_config" && \
grep -Fxq " UseKeychain yes" "$ssh_config" && \
grep -Fxq " IdentityFile $SSH_PRIVATE_KEY" "$ssh_config"
}

# Generate SSH key pair
# Returns: 0 on success, 1 on failure
generate_ssh_keys() {
get_ssh_key_paths

# Generate a 2048-bit RSA SSH key pair
# -q makes the process quiet
# -N '' sets an empty passphrase
# -f specifies the output file
ssh-keygen -q -t rsa -b 2048 -N '' -f "$SSH_PRIVATE_KEY" <<< y >/dev/null 2>&1
}

# Add SSH key to ssh-agent and Keychain
# Returns: 0 on success, 1 on failure
add_ssh_key_to_agent() {
get_ssh_key_paths

# Start ssh-agent if not running
if ! ssh-add -l >/dev/null 2>&1; then
eval "$(ssh-agent -s)" >/dev/null
fi

# Add key to agent and keychain
# Note: -K flag is deprecated on newer macOS, use --apple-use-keychain instead
if command -v sw_vers >/dev/null 2>&1 && [[ $(sw_vers -productVersion | cut -d. -f1) -ge 12 ]]; then
# macOS 12 (Monterey) and later
ssh-add --apple-use-keychain "$SSH_PRIVATE_KEY" 2>/dev/null
else
# Older macOS versions
ssh-add -K "$SSH_PRIVATE_KEY" 2>/dev/null
fi
}
Loading