Saves ~/.claude/settings.json (user-level prefs + Bash/Read permissions allowlist) so the same Claude Code config can be replicated on other hosts via install.sh. What gets deployed: - .claude/settings.json — theme, verbose, defaultMode, skipAutoPermissionPrompt and the cumulative "approve once" allowlist for common ops (systemctl, iptables, journalctl, git, curl, etc.). Server-specific allow entries (e.g. /home/mastodon/* paths) are dead weight elsewhere but harmless. - .claude/.gitignore — explicit deny-list so a future `git add .claude` doesn't accidentally pull in credentials, session logs, project memories, file-history, telemetry, caches, or settings.local.json. What is NOT tracked (by design): - .credentials.json (auth) - history.jsonl, sessions/, projects/ (chat data, project memories) - settings.local.json (per-machine overrides — by Claude Code convention) - file-history/, plans/, paste-cache/, shell-snapshots/, session-env/, telemetry/, downloads/, cache/, backups/, mcp-needs-auth-cache.json - plugins/ (marketplace install paths are absolute and host-specific) deploy_dir's merge semantics mean: on a host that already has ~/.claude/.credentials.json or ~/.claude/projects/, those stay untouched because the repo doesn't track them. Only the files we explicitly include get installed. Existing settings.json on the target host is moved aside to ~/.drunkendotfiles.bak.<timestamp>/ before being replaced.
225 lines
7.7 KiB
Bash
Executable File
225 lines
7.7 KiB
Bash
Executable File
#!/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/<file>.
|
|
# 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/<dir>/, 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/<rel> 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/<rel> to $HOME/<rel>. 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/<rel> 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/<version>.
|
|
# 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."
|