Skip to content

Instantly share code, notes, and snippets.

@shoveller
Last active August 28, 2025 15:42
Show Gist options
  • Select an option

  • Save shoveller/dee67d5961fa73dd82d2ebbf00a757a7 to your computer and use it in GitHub Desktop.

Select an option

Save shoveller/dee67d5961fa73dd82d2ebbf00a757a7 to your computer and use it in GitHub Desktop.
Robust shell script to clone ccpm repository and setup Claude configuration
#!/bin/bash
#
# Claude Configuration Setup Script
#
# Description: Clones the ccpm repository and copies the .claude configuration
# to the current directory for Claude IDE integration.
#
# Author: Automated script generation
# Version: 1.0
# Created: $(date +%Y-%m-%d)
#
# Usage: ./setup-claude-config.sh [OPTIONS]
# ./setup-claude-config.sh --help
#
# Exit Codes:
# 0 - Success
# 1 - General error
# 2 - Missing dependencies
# 3 - Network/clone error
# 4 - File operation error
# 5 - Permission error
# 6 - Invalid arguments
#
# Enable strict error handling and security
set -euo pipefail
# Set secure umask
umask 077
# Script configuration
readonly SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_VERSION="1.0"
readonly DEFAULT_REPO_URL="https://github.com/automazeio/ccpm.git"
readonly DEFAULT_TEMP_DIR="/tmp/ccpm"
readonly DEFAULT_SOURCE_PATH="/.claude"
readonly DEFAULT_DEST_PATH="./"
# Global variables
REPO_URL="$DEFAULT_REPO_URL"
TEMP_DIR="$DEFAULT_TEMP_DIR"
SOURCE_PATH="$DEFAULT_SOURCE_PATH"
DEST_PATH="$DEFAULT_DEST_PATH"
VERBOSE=false
QUIET=false
FORCE=false
BACKUP=false
LOG_FILE=""
# Color codes for output (if terminal supports it)
if [[ -t 1 ]]; then
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color
else
readonly RED=''
readonly GREEN=''
readonly YELLOW=''
readonly BLUE=''
readonly NC=''
fi
# Logging functions
log_info() {
if [[ "$QUIET" != true ]]; then
echo -e "${BLUE}[INFO]${NC} $1" >&2
[[ -n "$LOG_FILE" ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $1" >> "$LOG_FILE"
fi
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1" >&2
[[ -n "$LOG_FILE" ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARN: $1" >> "$LOG_FILE"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
[[ -n "$LOG_FILE" ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >> "$LOG_FILE"
}
log_success() {
if [[ "$QUIET" != true ]]; then
echo -e "${GREEN}[SUCCESS]${NC} $1" >&2
[[ -n "$LOG_FILE" ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] SUCCESS: $1" >> "$LOG_FILE"
fi
}
log_debug() {
if [[ "$VERBOSE" == true ]]; then
echo -e "${BLUE}[DEBUG]${NC} $1" >&2
[[ -n "$LOG_FILE" ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] DEBUG: $1" >> "$LOG_FILE"
fi
}
# Cleanup function
cleanup_temp_dir() {
if [[ -d "$TEMP_DIR" ]]; then
# Safety check: ensure we're only removing directories under /tmp
if [[ "$TEMP_DIR" == /tmp/* ]]; then
log_debug "Cleaning up temporary directory: $TEMP_DIR"
rm -rf "$TEMP_DIR"
log_debug "Temporary directory cleaned up"
else
log_warn "Skipping cleanup of suspicious path: $TEMP_DIR"
fi
fi
}
# Exit handler
cleanup_and_exit() {
local exit_code=${1:-1}
log_debug "Script exiting with code $exit_code"
cleanup_temp_dir
exit "$exit_code"
}
# Set up signal handlers
trap 'cleanup_and_exit 1' INT TERM
# Display help
show_help() {
cat << EOF
$SCRIPT_NAME v$SCRIPT_VERSION
USAGE:
$SCRIPT_NAME [OPTIONS]
DESCRIPTION:
Clones the ccpm repository and copies the .claude configuration directory
to the current directory for Claude IDE integration.
OPTIONS:
-r, --repo-url URL Repository URL to clone (default: $DEFAULT_REPO_URL)
-t, --temp-dir DIR Temporary directory path (default: $DEFAULT_TEMP_DIR)
-d, --dest-path DIR Destination path (default: $DEFAULT_DEST_PATH)
-f, --force Force overwrite existing .claude directory
-b, --backup Create backup of existing .claude directory
-v, --verbose Enable verbose output
-q, --quiet Suppress non-error output
-l, --log-file FILE Log operations to specified file
-h, --help Show this help message
--version Show version information
EXIT CODES:
0 Success
1 General error
2 Missing dependencies
3 Network/clone error
4 File operation error
5 Permission error
6 Invalid arguments
EXAMPLES:
$SCRIPT_NAME # Basic usage
$SCRIPT_NAME --verbose # Verbose output
$SCRIPT_NAME --backup --force # Backup existing and force overwrite
$SCRIPT_NAME --repo-url https://github.com/user/repo.git # Custom repository
REQUIREMENTS:
- bash (version 4.0+)
- git (any recent version)
- cp, rm, mkdir (coreutils)
- Network connectivity
EOF
}
# Show version
show_version() {
echo "$SCRIPT_NAME v$SCRIPT_VERSION"
}
# Validate dependencies
check_dependencies() {
log_debug "Checking dependencies"
local missing_deps=()
# Check for required commands
for cmd in git cp rm mkdir chmod; do
if ! command -v "$cmd" &> /dev/null; then
missing_deps+=("$cmd")
fi
done
if [[ ${#missing_deps[@]} -gt 0 ]]; then
log_error "Missing required dependencies: ${missing_deps[*]}"
log_error "Please install the missing dependencies and try again"
cleanup_and_exit 2
fi
# Check bash version
if [[ ${BASH_VERSION%%.*} -lt 4 ]]; then
log_warn "Bash version ${BASH_VERSION} detected. Version 4.0+ recommended"
fi
log_debug "All dependencies satisfied"
}
# Validate input parameters
validate_inputs() {
log_debug "Validating input parameters"
# Validate repository URL
if [[ ! "$REPO_URL" =~ ^https?://|^git@ ]]; then
log_error "Invalid repository URL format: $REPO_URL"
cleanup_and_exit 6
fi
# Validate temporary directory path
if [[ ! "$TEMP_DIR" =~ ^/tmp/ ]]; then
log_error "Temporary directory must be under /tmp for security: $TEMP_DIR"
cleanup_and_exit 6
fi
# Check if destination is writable
if [[ ! -w "$(dirname "$DEST_PATH")" ]]; then
log_error "Destination directory is not writable: $(dirname "$DEST_PATH")"
cleanup_and_exit 5
fi
# Check disk space (require at least 100MB free in /tmp)
local available_space
available_space=$(df /tmp | awk 'NR==2 {print $4}')
if [[ $available_space -lt 100000 ]]; then
log_warn "Low disk space in /tmp: ${available_space}KB available"
fi
log_debug "Input validation passed"
}
# Check network connectivity
check_network() {
log_debug "Checking network connectivity"
# Extract hostname from repository URL
local hostname
if [[ "$REPO_URL" =~ ^https://([^/]+) ]]; then
hostname="${BASH_REMATCH[1]}"
elif [[ "$REPO_URL" =~ ^git@([^:]+): ]]; then
hostname="${BASH_REMATCH[1]}"
else
log_warn "Could not extract hostname from repository URL for connectivity check"
return 0
fi
# Test connectivity (timeout after 10 seconds)
if ! timeout 10 bash -c "</dev/tcp/$hostname/443" 2>/dev/null &&
! timeout 10 bash -c "</dev/tcp/$hostname/22" 2>/dev/null; then
log_error "Cannot connect to $hostname. Check network connectivity"
cleanup_and_exit 3
fi
log_debug "Network connectivity verified"
}
# Create backup of existing .claude directory
create_backup() {
local claude_dir="$DEST_PATH/.claude"
if [[ -d "$claude_dir" ]]; then
local backup_dir="$claude_dir.backup.$(date +%Y%m%d-%H%M%S)"
log_info "Creating backup of existing .claude directory: $backup_dir"
if ! cp -r "$claude_dir" "$backup_dir"; then
log_error "Failed to create backup of existing .claude directory"
cleanup_and_exit 4
fi
log_success "Backup created: $backup_dir"
fi
}
# Clone repository
clone_repository() {
log_info "Cloning repository: $REPO_URL"
log_debug "Target directory: $TEMP_DIR"
# Remove existing temporary directory if it exists
if [[ -d "$TEMP_DIR" ]]; then
log_warn "Temporary directory already exists: $TEMP_DIR"
cleanup_temp_dir
fi
# Clone repository with progress output control
local git_opts=()
if [[ "$QUIET" == true ]]; then
git_opts+=(--quiet)
elif [[ "$VERBOSE" == true ]]; then
git_opts+=(--verbose)
fi
# Perform the clone
if ! git clone "${git_opts[@]}" "$REPO_URL" "$TEMP_DIR"; then
log_error "Failed to clone repository from $REPO_URL"
log_error "Check repository URL, network connectivity, and authentication"
cleanup_and_exit 3
fi
log_success "Repository cloned successfully"
}
# Copy .claude directory
copy_claude_directory() {
local source_dir="$TEMP_DIR$SOURCE_PATH"
local dest_dir="$DEST_PATH/.claude"
log_info "Copying .claude configuration"
log_debug "Source: $source_dir"
log_debug "Destination: $dest_dir"
# Check if source directory exists
if [[ ! -d "$source_dir" ]]; then
log_error ".claude directory not found in cloned repository: $source_dir"
log_error "Repository may not contain Claude configuration"
cleanup_and_exit 4
fi
# Handle existing destination directory
if [[ -d "$dest_dir" ]]; then
if [[ "$BACKUP" == true ]]; then
create_backup
fi
if [[ "$FORCE" == true ]]; then
log_warn "Removing existing .claude directory"
rm -rf "$dest_dir"
else
log_error ".claude directory already exists: $dest_dir"
log_error "Use --force to overwrite or --backup to create backup first"
cleanup_and_exit 4
fi
fi
# Perform the copy
if ! cp -r "$source_dir" "$dest_dir"; then
log_error "Failed to copy .claude directory"
cleanup_and_exit 4
fi
# Set appropriate permissions
chmod -R u+rw,go-rwx "$dest_dir" 2>/dev/null || {
log_warn "Could not set restrictive permissions on .claude directory"
}
log_success ".claude directory copied successfully"
}
# Parse command line arguments
parse_arguments() {
while [[ $# -gt 0 ]]; do
case $1 in
-r|--repo-url)
[[ -n "${2:-}" ]] || { log_error "Repository URL required for $1"; cleanup_and_exit 6; }
REPO_URL="$2"
shift 2
;;
-t|--temp-dir)
[[ -n "${2:-}" ]] || { log_error "Temporary directory required for $1"; cleanup_and_exit 6; }
TEMP_DIR="$2"
shift 2
;;
-d|--dest-path)
[[ -n "${2:-}" ]] || { log_error "Destination path required for $1"; cleanup_and_exit 6; }
DEST_PATH="$2"
shift 2
;;
-f|--force)
FORCE=true
shift
;;
-b|--backup)
BACKUP=true
shift
;;
-v|--verbose)
VERBOSE=true
shift
;;
-q|--quiet)
QUIET=true
shift
;;
-l|--log-file)
[[ -n "${2:-}" ]] || { log_error "Log file path required for $1"; cleanup_and_exit 6; }
LOG_FILE="$2"
# Create log file and test write access
if ! touch "$LOG_FILE" 2>/dev/null; then
log_error "Cannot write to log file: $LOG_FILE"
cleanup_and_exit 5
fi
shift 2
;;
-h|--help)
show_help
exit 0
;;
--version)
show_version
exit 0
;;
*)
log_error "Unknown argument: $1"
log_error "Use --help for usage information"
cleanup_and_exit 6
;;
esac
done
# Validate conflicting options
if [[ "$VERBOSE" == true && "$QUIET" == true ]]; then
log_error "Cannot use --verbose and --quiet together"
cleanup_and_exit 6
fi
}
# Main execution function
main() {
log_info "Starting Claude configuration setup"
log_debug "Script version: $SCRIPT_VERSION"
# Pre-execution checks
check_dependencies
validate_inputs
check_network
# Core operations
clone_repository
copy_claude_directory
# Cleanup
cleanup_temp_dir
log_success "Claude configuration setup completed successfully"
log_info ".claude directory is now available in: $(realpath "$DEST_PATH/.claude")"
return 0
}
# Script entry point
if [[ "${BASH_SOURCE[0]:-$0}" == "${0}" ]]; then
# Parse command line arguments
parse_arguments "$@"
# Execute main function
main
# Exit with success
exit 0
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment