#!/usr/bin/env bash # # drunkendotfiles installer # # Usage: # curl -fsSL https://gitea.dlw.la/dissimulo/drunkendotfiles/raw/branch/drunkendotfiles/install.sh | bash # # Or clone first and run locally: # git clone https://gitea.dlw.la/dissimulo/drunkendotfiles.git ~/.yadr && ~/.yadr/install.sh # set -euo pipefail REPO_URL="${DRUNKENDOTFILES_REPO:-https://gitea.dlw.la/dissimulo/drunkendotfiles.git}" YADR_DIR="$HOME/.yadr" BACKUP_DIR="$HOME/.drunkendotfiles.bak.$(date +%Y%m%d-%H%M%S)" # Files at the repo root (relative to $YADR_DIR) that get placed at $HOME/. # Includes real files, relative symlinks into .yadr, and .backup copies. PERSONAL_FILES=( .bash_logout .bashrc .fehbg .gopherrc .jigdo-lite .nanorc .profile .rainbow_config.json .selected_editor .tmux.conf .weatherspect .xmodmap .xscreensaver .xsnowrc .zlogin.backup .zlogout.backup .zpreztorc.backup .zprofile.backup .zshenv.backup .zshrc.backup # Relative symlinks into .yadr/... — YADR's rake install creates equivalents, # but we deploy these too so things work even if rake install is skipped. .aprc .ctags .editrc .escaped_colors.rb .gemrc .gitconfig .inputrc .pryrc .rdebugrc .unescaped_colors.rb .vim .vimrc .zlogin .zlogout .zpreztorc .zprofile .zshenv .zshrc ) # Directories whose contents are merged into $HOME//, file by file. Files # the repo provides replace conflicting on-disk versions (with the displaced # version preserved in $BACKUP_DIR); files outside the repo's tree are left # untouched. This deliberately avoids wholesale-replacing $HOME/.local etc., # which would sweep away unrelated user data (fonts, app state, ...). PERSONAL_DIRS=( .fonts .irssi .nano .themes .local .mplayer .claude ) have() { command -v "$1" >/dev/null 2>&1; } log() { printf '==> %s\n' "$*"; } info() { printf ' %s\n' "$*"; } warn() { printf '!! %s\n' "$*" >&2; } require() { have "$1" || { warn "missing required command: $1"; exit 1; } } require git log "drunkendotfiles installer" log "repo: $REPO_URL" log "target: $YADR_DIR" log "backup: $BACKUP_DIR (created on first collision)" echo # 1. Fetch the repo into $HOME/.yadr if [ -d "$YADR_DIR/.git" ]; then log "Repo already cloned — pulling latest" git -C "$YADR_DIR" pull --ff-only elif [ -d "$YADR_DIR" ]; then warn "$YADR_DIR exists but isn't a git repo. Refusing to overwrite." exit 1 else log "Cloning drunkendotfiles" git clone --recurse-submodules "$REPO_URL" "$YADR_DIR" fi cd "$YADR_DIR" # 1a. Ensure submodules are initialized (Prezto lives in zsh/prezto as a # submodule — without it, ~/.zshrc fails with "no such file or directory: # ~/.zprezto/runcoms/zshrc"). Safe to run on fresh and existing clones alike. log "Ensuring submodules are initialized" git submodule update --init --recursive # 2. Run YADR's native install (Vim plugins, prezto, etc.) if have rake; then log "Running YADR rake install" [ "${1:-}" = "ask" ] && export ASK="true" rake install || warn "rake install returned non-zero; continuing" else warn "rake not found — skipping YADR rake install. Install ruby+rake and re-run this script to finish YADR setup." fi # 3. Deploy personal dotfiles # Move $HOME/ aside into $BACKUP_DIR, preserving its sub-path. backup_one_path() { local rel="$1" local dst="$HOME/$rel" if [ -e "$dst" ] || [ -L "$dst" ]; then mkdir -p "$BACKUP_DIR/$(dirname "$rel")" mv "$dst" "$BACKUP_DIR/$rel" fi } # Returns 0 iff $1 and $2 are equivalent (same symlink target, or identical # regular-file content). Used to make re-runs idempotent: unchanged files get # skipped instead of needlessly backed up. paths_equivalent() { local a="$1" b="$2" if [ -L "$a" ] && [ -L "$b" ]; then [ "$(readlink "$a")" = "$(readlink "$b")" ] elif [ -L "$a" ] || [ -L "$b" ]; then return 1 elif [ -f "$a" ] && [ -f "$b" ]; then cmp -s "$a" "$b" else return 1 fi } # Deploy one file or symlink at $YADR_DIR/ to $HOME/. If a different # file/symlink is already at the destination, it's moved into $BACKUP_DIR first. deploy_file() { local rel="$1" local src="$YADR_DIR/$rel" local dst="$HOME/$rel" if [ ! -e "$src" ] && [ ! -L "$src" ]; then return fi if paths_equivalent "$src" "$dst"; then return fi backup_one_path "$rel" mkdir -p "$(dirname "$dst")" cp -a "$src" "$dst" info "$rel" } # Deploy a directory by walking its tree and deploying each file/symlink in # turn. Directories get mkdir'd; existing entries inside $HOME/ that the # repo doesn't know about are left alone. This is the merge semantics that # protects e.g. $HOME/.local/share/fonts from being clobbered when the repo # only tracks $HOME/.local/bin. deploy_dir() { local rel="$1" local src="$YADR_DIR/$rel" if [ ! -d "$src" ] || [ -L "$src" ]; then return fi mkdir -p "$HOME/$rel" local sub sub_rel src_path while IFS= read -r -d '' sub; do sub="${sub#./}" [ "$sub" = "." ] && continue sub_rel="$rel/$sub" src_path="$YADR_DIR/$sub_rel" if [ -d "$src_path" ] && [ ! -L "$src_path" ]; then mkdir -p "$HOME/$sub_rel" continue fi deploy_file "$sub_rel" done < <(cd "$src" && find . -mindepth 1 -print0) } log "Deploying personal dotfiles" for f in "${PERSONAL_FILES[@]}"; do deploy_file "$f"; done for d in "${PERSONAL_DIRS[@]}"; do deploy_dir "$d"; done # .local/bin/claude is a relative symlink to ../share/claude/versions/. # The version baked into the repo snapshot is whatever was current when this # was last committed; on a fresh host that exact version probably isn't # installed. Resolve as follows: # 1. If the deployed symlink already resolves, leave it alone. # 2. Else, if any other Claude version is present under # ~/.local/share/claude/versions/, repoint the symlink to the highest # one (semver sort) so users get the latest installed binary. # 3. Else, drop the dangling link and let Claude Code's own installer # (next step) recreate it. claude_link="$HOME/.local/bin/claude" claude_versions_dir="$HOME/.local/share/claude/versions" if [ -L "$claude_link" ] && [ ! -e "$claude_link" ]; then latest_claude="" if [ -d "$claude_versions_dir" ]; then latest_claude="$(ls -1 "$claude_versions_dir" 2>/dev/null | sort -V | tail -1)" fi if [ -n "$latest_claude" ] && [ -e "$claude_versions_dir/$latest_claude" ]; then ln -sfn "../share/claude/versions/$latest_claude" "$claude_link" info "Repointed ~/.local/bin/claude -> $latest_claude (latest installed)" else rm "$claude_link" fi fi # 4. Install Claude Code (Anthropic's CLI). Skip if already installed or # explicitly opted out via DRUNKENDOTFILES_SKIP_CLAUDE=1. if [ "${DRUNKENDOTFILES_SKIP_CLAUDE:-0}" = "1" ]; then info "Skipping Claude Code install (DRUNKENDOTFILES_SKIP_CLAUDE=1)" elif have claude || [ -x "$HOME/.local/bin/claude" ]; then info "Claude Code already installed ($(claude --version 2>/dev/null || echo 'version unknown'))" else log "Installing Claude Code" if have curl; then curl -fsSL https://claude.ai/install.sh | bash \ || warn "Claude Code install failed; continuing. Install manually with: curl -fsSL https://claude.ai/install.sh | bash" else warn "curl not found; skipping Claude Code install" fi fi # Ensure +x on user scripts if [ -d "$HOME/.local/bin" ]; then find "$HOME/.local/bin" -maxdepth 1 -type f -exec chmod +x {} \; 2>/dev/null || true fi # 5. Refresh font cache so newly-deployed .fonts/ and .local/share/fonts/ # entries are visible to apps without a logout/login. if have fc-cache; then log "Refreshing font cache" fc-cache -f >/dev/null 2>&1 || warn "fc-cache returned non-zero; continuing" fi echo log "Done." if [ -d "$BACKUP_DIR" ]; then info "Files that would have been overwritten are backed up at:" info " $BACKUP_DIR" fi info "Log out and back in (or start a new shell) to pick up the new configs."