Last active
June 11, 2025 14:49
-
-
Save andrewpryor/f2d52f6016ddf64d8f4d5f4976a2b8de to your computer and use it in GitHub Desktop.
env-sync bootstrap script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# env-sync bootstrap script | |
# Usage: curl -sL https://gist.githubusercontent.com/andrewpryor/f2d52f6016ddf64d8f4d5f4976a2b8de/raw | bash | |
# Dry-run: curl -sL https://gist.githubusercontent.com/andrewpryor/f2d52f6016ddf64d8f4d5f4976a2b8de/raw | bash -s -- --dry-run | |
# Last updated: 2025-06-11 09:49:23 CDT | |
set -euo pipefail | |
# Parse command line arguments | |
DRY_RUN=false | |
FORCE=false | |
while [[ $# -gt 0 ]]; do | |
case $1 in | |
--dry-run) | |
DRY_RUN=true | |
shift | |
;; | |
--force) | |
FORCE=true | |
shift | |
;; | |
-h|--help) | |
echo "env-sync bootstrap script" | |
echo "Usage: $0 [OPTIONS]" | |
echo "" | |
echo "OPTIONS:" | |
echo " --dry-run Show what would be done without making changes" | |
echo " --force Force reinstallation even if already present" | |
echo " --help Show this help message" | |
exit 0 | |
;; | |
*) | |
echo "Unknown option: $1" | |
exit 1 | |
;; | |
esac | |
done | |
# Source color codes if available, otherwise define minimal set | |
if [ -f "$HOME/env-sync/helpers/10_colors.sh" ]; then | |
source "$HOME/env-sync/helpers/10_colors.sh" | |
else | |
# Minimal color definitions for bootstrap | |
cRED="\e[31m" | |
cGREEN="\e[32m" | |
cYELLOW="\e[33m" | |
cBLUE="\e[34m" | |
cRESET="\e[0m" | |
fi | |
# Helper functions | |
print_status() { | |
echo -e "${cBLUE}[*]${cRESET} $1" | |
} | |
print_success() { | |
echo -e "${cGREEN}[✓]${cRESET} $1" | |
} | |
print_error() { | |
echo -e "${cRED}[!]${cRESET} $1" | |
} | |
print_warning() { | |
echo -e "${cYELLOW}[!]${cRESET} $1" | |
} | |
print_dry_run() { | |
echo -e "${cYELLOW}[DRY-RUN]${cRESET} $1" | |
} | |
# Execute command or show what would be done | |
execute_cmd() { | |
local cmd="$1" | |
local description="$2" | |
if [ "$DRY_RUN" = true ]; then | |
print_dry_run "Would run: $description" | |
print_dry_run "Command: $cmd" | |
else | |
print_status "$description" | |
eval "$cmd" | |
fi | |
} | |
# Detect OS | |
detect_os() { | |
if [ -f /etc/os-release ]; then | |
. /etc/os-release | |
OS=$ID | |
VER=$VERSION_ID | |
else | |
print_error "Cannot detect OS. /etc/os-release not found." | |
exit 1 | |
fi | |
print_status "Detected OS: $OS $VER" | |
} | |
# Detect architecture | |
detect_arch() { | |
ARCH=$(uname -m) | |
case $ARCH in | |
x86_64) | |
ARCH_TYPE="amd64" | |
;; | |
aarch64|arm64) | |
ARCH_TYPE="arm64" | |
;; | |
*) | |
print_error "Unsupported architecture: $ARCH" | |
exit 1 | |
;; | |
esac | |
print_status "Detected architecture: $ARCH_TYPE" | |
} | |
# Install GitHub CLI | |
install_gh() { | |
if command -v gh &> /dev/null; then | |
print_success "GitHub CLI already installed ($(gh --version | head -1))" | |
return 0 | |
fi | |
if [ "$DRY_RUN" = true ]; then | |
print_dry_run "Would install GitHub CLI for $OS $ARCH_TYPE" | |
return 0 | |
fi | |
print_status "Installing GitHub CLI..." | |
case $OS in | |
ubuntu|pop|debian) | |
# Check if repository already exists | |
if [ ! -f /etc/apt/sources.list.d/github-cli.list ]; then | |
execute_cmd "curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg" "Adding GitHub CLI GPG key" | |
execute_cmd "sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg" "Setting GPG key permissions" | |
execute_cmd "echo 'deb [arch=\$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main' | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null" "Adding GitHub CLI repository" | |
else | |
print_success "GitHub CLI repository already configured" | |
fi | |
execute_cmd "sudo apt update" "Updating package lists" | |
execute_cmd "sudo apt install gh -y" "Installing GitHub CLI" | |
;; | |
*) | |
print_warning "Automatic installation not supported for $OS" | |
print_status "Attempting manual installation..." | |
# Direct binary download | |
GH_VERSION=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/') | |
execute_cmd "curl -Lo gh.tar.gz 'https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_${ARCH_TYPE}.tar.gz'" "Downloading GitHub CLI binary" | |
execute_cmd "tar -xzf gh.tar.gz" "Extracting GitHub CLI" | |
execute_cmd "sudo mv gh_${GH_VERSION}_linux_${ARCH_TYPE}/bin/gh /usr/local/bin/" "Installing GitHub CLI binary" | |
execute_cmd "rm -rf gh.tar.gz gh_${GH_VERSION}_linux_${ARCH_TYPE}" "Cleaning up temporary files" | |
;; | |
esac | |
if command -v gh &> /dev/null; then | |
print_success "GitHub CLI installed successfully ($(gh --version | head -1))" | |
else | |
print_error "Failed to install GitHub CLI" | |
exit 1 | |
fi | |
} | |
# Authenticate with GitHub | |
authenticate_gh() { | |
print_status "Checking GitHub authentication..." | |
if gh auth status &> /dev/null; then | |
local auth_user=$(gh auth status 2>&1 | grep "Logged in to" | sed 's/.*as \([^[:space:]]*\).*/\1/') | |
print_success "Already authenticated with GitHub as $auth_user" | |
return 0 | |
fi | |
if [ "$DRY_RUN" = true ]; then | |
print_dry_run "Would prompt for GitHub authentication" | |
print_dry_run "Authentication method: web browser flow" | |
return 0 | |
fi | |
print_warning "GitHub authentication required" | |
print_status "You will now be prompted to authenticate with GitHub" | |
# Detect if we're in SSH/headless environment | |
if [ -n "${SSH_CONNECTION:-}" ] || [ -z "${DISPLAY:-}" ]; then | |
print_status "Detected headless/SSH session. Using device code flow..." | |
gh auth login --web --git-protocol https | |
else | |
print_status "Using web browser authentication..." | |
gh auth login --web --git-protocol https | |
fi | |
if gh auth status &> /dev/null; then | |
local auth_user=$(gh auth status 2>&1 | grep "Logged in to" | sed 's/.*as \([^[:space:]]*\).*/\1/') | |
print_success "GitHub authentication successful as $auth_user" | |
else | |
print_error "GitHub authentication failed" | |
exit 1 | |
fi | |
} | |
# Clone env-sync repository | |
clone_env_sync() { | |
ENV_SYNC_DIR="$HOME/env-sync" | |
if [ -d "$ENV_SYNC_DIR/.git" ]; then | |
print_success "env-sync already exists at $ENV_SYNC_DIR" | |
if [ "$DRY_RUN" = true ]; then | |
print_dry_run "Would update existing env-sync installation" | |
print_dry_run "Would preserve: env-sync.conf, env/ directory" | |
return 0 | |
fi | |
if [ "$FORCE" = true ]; then | |
print_warning "Force flag set - updating env-sync installation" | |
else | |
print_status "Updating existing env-sync installation..." | |
fi | |
# Create backup | |
local backup_dir="/tmp/env-sync-backup-$(date +%Y%m%d_%H%M%S)" | |
print_status "Creating backup at $backup_dir" | |
mkdir -p "$backup_dir" | |
# Backup critical files | |
[ -f "$ENV_SYNC_DIR/env-sync.conf" ] && cp "$ENV_SYNC_DIR/env-sync.conf" "$backup_dir/" | |
[ -d "$ENV_SYNC_DIR/env" ] && cp -r "$ENV_SYNC_DIR/env" "$backup_dir/" | |
# Update via git pull instead of destructive re-clone | |
cd "$ENV_SYNC_DIR" | |
if git remote get-url origin | grep -q "github.com/andrewpryor/env-sync"; then | |
print_status "Updating via git pull..." | |
git pull --rebase | |
else | |
print_warning "Repository origin doesn't match expected URL - skipping update" | |
fi | |
# Restore backed up files | |
[ -f "$backup_dir/env-sync.conf" ] && cp "$backup_dir/env-sync.conf" "$ENV_SYNC_DIR/" | |
[ -d "$backup_dir/env" ] && cp -r "$backup_dir/env/" "$ENV_SYNC_DIR/" | |
print_success "env-sync updated successfully (backup at $backup_dir)" | |
else | |
if [ "$DRY_RUN" = true ]; then | |
print_dry_run "Would clone andrewpryor/env-sync to $ENV_SYNC_DIR" | |
return 0 | |
fi | |
print_status "Cloning env-sync repository..." | |
gh repo clone andrewpryor/env-sync "$ENV_SYNC_DIR" | |
print_success "env-sync cloned to $ENV_SYNC_DIR" | |
fi | |
} | |
# Run env-sync | |
run_env_sync() { | |
if [ "$DRY_RUN" = true ]; then | |
print_dry_run "Would run env-sync setup (./env-sync.sh)" | |
return 0 | |
fi | |
print_status "Running env-sync setup..." | |
cd "$HOME/env-sync" | |
if [ -f "./env-sync.sh" ]; then | |
bash ./env-sync.sh | |
else | |
print_error "env-sync.sh not found!" | |
exit 1 | |
fi | |
} | |
# Main execution | |
main() { | |
echo -e "${cGREEN}╔══════════════════════════════════════╗${cRESET}" | |
echo -e "${cGREEN}║ env-sync Bootstrap Script ║${cRESET}" | |
echo -e "${cGREEN}╚══════════════════════════════════════╝${cRESET}" | |
echo | |
if [ "$DRY_RUN" = true ]; then | |
echo -e "${cYELLOW} DRY-RUN MODE${cRESET}" | |
echo -e "${cYELLOW} No changes will be made${cRESET}" | |
echo | |
fi | |
# Check if running as root | |
if [ "$EUID" -eq 0 ]; then | |
print_error "Do not run this script as root" | |
exit 1 | |
fi | |
# Step 1: Detect system | |
detect_os | |
detect_arch | |
# Step 2: Install GitHub CLI | |
install_gh | |
# Step 3: Authenticate | |
authenticate_gh | |
# Step 4: Clone repository | |
clone_env_sync | |
# Step 5: Run env-sync | |
run_env_sync | |
if [ "$DRY_RUN" = true ]; then | |
print_success "Dry-run completed successfully!" | |
echo | |
echo "To actually run the bootstrap:" | |
echo " curl -sL https://gist.githubusercontent.com/andrewpryor/f2d52f6016ddf64d8f4d5f4976a2b8de/raw | bash" | |
else | |
print_success "Bootstrap completed successfully!" | |
fi | |
} | |
# Run main function | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment