Last active
June 19, 2026 23:11
-
-
Save davelevine/bbbcf9e8f492ae12c2914b55b1d6e7f6 to your computer and use it in GitHub Desktop.
davelevine/dotfiles — guided fresh-machine bootstrap
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 | |
| # | |
| # install.sh — guided fresh-machine bootstrap for davelevine/dotfiles (private). | |
| # | |
| # bash <(curl -fsSL https://gist.githubusercontent.com/davelevine/bbbcf9e8f492ae12c2914b55b1d6e7f6/raw/install.sh) | |
| # | |
| # macOS: installs Homebrew + chezmoi + gh, signs in to GitHub (so the private | |
| # dotfiles repo can be cloned), then applies the dotfiles — which install every | |
| # package in the Brewfile via chezmoi's run-scripts. | |
| # Linux: installs chezmoi (get.chezmoi.io) + git, authenticates to GitHub (gh if | |
| # present, otherwise a Personal Access Token), then applies the dotfiles. This | |
| # is a lightweight fallback for boxes outside the Ansible fleet; managed Linux | |
| # hosts are still provisioned by the Ansible `dotfiles` role (which also | |
| # templates secrets from Bitwarden — this script leaves them empty). | |
| # Pass -y / --yes to accept all yes/no prompts automatically. | |
| # | |
| set -euo pipefail | |
| CHEZMOI_REPO="davelevine" # chezmoi shorthand → github.com/davelevine/dotfiles | |
| BREW="/opt/homebrew/bin/brew" # Apple Silicon | |
| case "$(uname -s)" in | |
| Darwin) PLATFORM=mac ;; | |
| Linux) PLATFORM=linux ;; | |
| *) printf 'Unsupported OS: %s\n' "$(uname -s)" >&2; exit 1 ;; | |
| esac | |
| # ── pretty output ──────────────────────────────────────────────────────────── | |
| if [ -t 1 ]; then | |
| bold=$(tput bold); dim=$(tput dim); reset=$(tput sgr0) | |
| blue=$(tput setaf 4); green=$(tput setaf 2); yellow=$(tput setaf 3) | |
| else | |
| bold=; dim=; reset=; blue=; green=; yellow= | |
| fi | |
| step() { printf '\n%s==>%s %s%s%s\n' "$bold$blue" "$reset" "$bold" "$*" "$reset"; } | |
| ok() { printf '%s ✓%s %s\n' "$green" "$reset" "$*"; } | |
| warn() { printf '%s !%s %s\n' "$yellow" "$reset" "$*"; } | |
| ASSUME_YES=0 | |
| case "${1:-}" in -y|--yes) ASSUME_YES=1 ;; esac | |
| ask() { # ask "Question" → returns 0 (yes) / 1 (no) | |
| [ "$ASSUME_YES" = 1 ] && return 0 | |
| [ -e /dev/tty ] || return 0 # no terminal (piped/automation) → proceed | |
| local ans | |
| printf '%s?%s %s %s(y/N)%s ' "$bold$yellow" "$reset" "$1" "$dim" "$reset" >/dev/tty | |
| read -r ans </dev/tty || true | |
| [[ "$ans" =~ ^[Yy] ]] | |
| } | |
| # Install OS packages via whatever package manager Linux provides. | |
| linux_install_pkgs() { | |
| if command -v apt-get >/dev/null 2>&1; then sudo apt-get update -qq && sudo apt-get install -y "$@" | |
| elif command -v dnf >/dev/null 2>&1; then sudo dnf install -y "$@" | |
| elif command -v yum >/dev/null 2>&1; then sudo yum install -y "$@" | |
| elif command -v pacman >/dev/null 2>&1; then sudo pacman -Sy --noconfirm "$@" | |
| elif command -v zypper >/dev/null 2>&1; then sudo zypper install -y "$@" | |
| elif command -v apk >/dev/null 2>&1; then sudo apk add "$@" | |
| else warn "No known package manager — install manually: $*"; return 1 | |
| fi | |
| } | |
| # ── banner ─────────────────────────────────────────────────────────────────── | |
| cat <<BANNER | |
| ${bold}${blue}🍺 davelevine/dotfiles — guided setup${reset} | |
| This will: | |
| • Install chezmoi (plus Homebrew & every Brewfile package on macOS) | |
| • Optionally update & upgrade existing packages (macOS) | |
| • Sign in to GitHub so the private repo can be cloned | |
| • Apply dotfiles for this host | |
| BANNER | |
| SECONDS=0 | |
| if [ "$PLATFORM" = mac ]; then | |
| # ── Homebrew ───────────────────────────────────────────────────────────── | |
| step "Homebrew" | |
| if [ -x "$BREW" ] || command -v brew >/dev/null 2>&1; then | |
| ok "already installed" | |
| elif ask "Homebrew is not installed. Install it now?"; then | |
| NONINTERACTIVE=1 /bin/bash -c \ | |
| "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" | |
| else | |
| warn "Homebrew is required — aborting."; exit 1 | |
| fi | |
| eval "$("$BREW" shellenv 2>/dev/null || brew shellenv)" | |
| export HOMEBREW_NO_ENV_HINTS=1 | |
| # ── update / upgrade ───────────────────────────────────────────────────── | |
| step "Update" | |
| if ask "Update Homebrew and upgrade existing packages first?"; then | |
| brew update && brew upgrade && ok "up to date" | |
| else | |
| export HOMEBREW_NO_AUTO_UPDATE=1 | |
| warn "skipped" | |
| fi | |
| # ── chezmoi + gh ───────────────────────────────────────────────────────── | |
| step "Tools" | |
| brew list chezmoi >/dev/null 2>&1 || brew install chezmoi | |
| brew list gh >/dev/null 2>&1 || brew install gh | |
| ok "chezmoi + gh ready" | |
| else | |
| # ── git + chezmoi (Linux) ──────────────────────────────────────────────── | |
| step "Tools" | |
| if ! command -v git >/dev/null 2>&1; then | |
| if ask "git is not installed. Install it now?"; then | |
| linux_install_pkgs git || { warn "git is required — aborting."; exit 1; } | |
| else | |
| warn "git is required — aborting."; exit 1 | |
| fi | |
| fi | |
| if command -v chezmoi >/dev/null 2>&1; then | |
| ok "chezmoi already installed" | |
| else | |
| # Official installer drops a static binary into ~/.local/bin (no root). | |
| sh -c "$(curl -fsLS get.chezmoi.io)" -- -b "$HOME/.local/bin" | |
| fi | |
| export PATH="$HOME/.local/bin:$PATH" | |
| ok "git + chezmoi ready" | |
| fi | |
| # ── GitHub sign-in (the dotfiles repo is private) ──────────────────────────── | |
| step "GitHub sign-in" | |
| if command -v gh >/dev/null 2>&1; then | |
| if gh auth status >/dev/null 2>&1; then | |
| ok "already signed in" | |
| else | |
| warn "Your dotfiles repo is private — opening GitHub sign-in." | |
| gh auth login --hostname github.com --git-protocol https --web | |
| fi | |
| gh auth setup-git # let git/chezmoi clone via the gh credential helper | |
| ok "git authenticated" | |
| elif [ -e /dev/tty ]; then | |
| # Linux without gh: authenticate with a Personal Access Token (HTTPS). | |
| warn "gh not found — authenticating with a GitHub Personal Access Token." | |
| warn "Create one (scope: repo) at https://github.com/settings/tokens" | |
| printf '%s?%s GitHub username: ' "$bold$yellow" "$reset" >/dev/tty | |
| read -r gh_user </dev/tty | |
| printf '%s?%s Personal Access Token (input hidden): ' "$bold$yellow" "$reset" >/dev/tty | |
| stty -echo 2>/dev/null || true; read -r gh_pat </dev/tty; stty echo 2>/dev/null || true | |
| printf '\n' >/dev/tty | |
| git config --global credential.helper store | |
| printf 'protocol=https\nhost=github.com\nusername=%s\npassword=%s\n\n' \ | |
| "$gh_user" "$gh_pat" | git credential approve | |
| unset gh_pat | |
| ok "git authenticated (token stored in ~/.git-credentials)" | |
| else | |
| warn "No gh and no terminal for a token prompt — assuming git credentials" | |
| warn "are already configured; the clone will fail if they are not." | |
| fi | |
| # ── apply dotfiles (+ Brewfile on macOS) ───────────────────────────────────── | |
| if [ "$PLATFORM" = mac ]; then | |
| step "Dotfiles & packages" | |
| apply_q="Apply dotfiles and install all packages now?" | |
| else | |
| step "Dotfiles" | |
| apply_q="Apply dotfiles now?" | |
| fi | |
| if ask "$apply_q"; then | |
| chezmoi init --apply "$CHEZMOI_REPO" | |
| ok "dotfiles applied" | |
| else | |
| warn "skipped — run later with: chezmoi init --apply $CHEZMOI_REPO" | |
| fi | |
| # ── summary ────────────────────────────────────────────────────────────────── | |
| mins=$((SECONDS / 60)); secs=$((SECONDS % 60)) | |
| cat <<DONE | |
| ${green}${bold}✅ Done in ${mins}m ${secs}s${reset} | |
| ${yellow}( (${reset} | |
| ${yellow}) )${reset} | |
| ${yellow}.______.${reset} | |
| ${yellow}| |]${reset} ${dim}thanks for using davelevine/dotfiles${reset} | |
| ${yellow}\\ /${reset} | |
| ${yellow}\`----'${reset} | |
| ${dim}Secrets (groq/ntfy) default to empty. To set them, create${reset} | |
| ${dim}~/.config/chezmoi/chezmoi.toml and run: chezmoi apply${reset} | |
| DONE |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment