Skip to content

Instantly share code, notes, and snippets.

@davelevine
Last active June 19, 2026 23:11
Show Gist options
  • Select an option

  • Save davelevine/bbbcf9e8f492ae12c2914b55b1d6e7f6 to your computer and use it in GitHub Desktop.

Select an option

Save davelevine/bbbcf9e8f492ae12c2914b55b1d6e7f6 to your computer and use it in GitHub Desktop.
davelevine/dotfiles — guided fresh-machine bootstrap
#!/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