diff --git a/Brewfile b/Brewfile index e473bae..4df7c68 100644 --- a/Brewfile +++ b/Brewfile @@ -6,15 +6,12 @@ brew "gh" brew "hashicorp/tap/terraform", trusted: true brew "htop" brew "jq" -brew "k9s" brew "lsd" brew "lua" brew "neovim" brew "mosh" brew "node" brew "postgresql@17" -brew "py-spy" -brew "pyenv" brew "tmux" brew "tree" brew "wget" diff --git a/assimilate.sh b/assimilate.sh index 336b923..cb94c0e 100755 --- a/assimilate.sh +++ b/assimilate.sh @@ -1,7 +1,10 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash set -ex +OS="$(uname -s)" +ARCH="$(uname -m)" + PREFIX="$HOME" DOTFILES=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) BACKUPS="$PREFIX/backups" @@ -24,6 +27,19 @@ function sym () { ln -s "$src" "$dest" } +function clone_pinned () { + url="$1"; dir="$2"; sha="$3" + # Fetch only the pinned commit, no full history. Avoids `git clone --revision` + # (git >= 2.49) so this works on older git too, e.g. AL2023's 2.40 on stale AMIs. + # Fetch-by-SHA relies on the server allowing reachable-SHA1-in-want (GitHub does). + if [ ! -d "$dir" ]; then + git init -q "$dir" + git -C "$dir" remote add origin "$url" + fi + git -C "$dir" fetch --depth 1 origin "$sha" + git -C "$dir" checkout -q "$sha" +} + if [ ! -e "$DOTFILES" ]; then echo "error: dotfiles/ needs to reside in $PREFIX" exit 1 @@ -31,40 +47,54 @@ fi mkdir -p "$BACKUPS/vim_backups" -sym bashrc .bashrc +# Shared symlinks (work on macOS and Linux) sym bash_profile .bash_profile sym gitconfig .gitconfig sym tmux.conf .tmux.conf sym zshrc .zshrc sym nvim .config/nvim -sym hammerspoon .hammerspoon -sym vscode/code_settings.json .vscode/settings.json -sym zed/settings.json .config/zed/settings.json -sym ghostty/config Library/Application\ Support/com.mitchellh.ghostty/config -sym tmux-powerline/config.sh .config/tmux-powerline/config.sh +sym tmux-powerline/config.sh .config/tmux-powerline/config.sh sym tmux-powerline/themes/theme.sh .config/tmux-powerline/themes/theme.sh -sym claude/statusline.sh .claude/statusline.sh -sym agent-instructions.md .claude/CLAUDE.md -sym agent-instructions.md .codex/AGENTS.md -sym zed/keymap.json .config/zed/keymap.json +sym claude/statusline.sh .claude/statusline.sh +sym agent-instructions.md .claude/CLAUDE.md +sym agent-instructions.md .codex/AGENTS.md + +# bashrc: symlinked on macOS; sourced from a stub on Linux. +# On the EC2 dev box, user_data appends a secrets/region block to .bashrc after +# this script runs. If .bashrc were a symlink into the repo, those appends would +# write into the dotfiles repo's tracked file. So on Linux, leave .bashrc as a +# regular file and source the dotfiles bashrc from it. +if [ "$OS" = "Darwin" ]; then + sym bashrc .bashrc +else + if ! grep -Fq 'dotfiles/bashrc' "$PREFIX/.bashrc" 2>/dev/null; then + echo '[ -f "$HOME/dotfiles/bashrc" ] && . "$HOME/dotfiles/bashrc"' >> "$PREFIX/.bashrc" + fi +fi -brew bundle install +# macOS-only symlinks and Homebrew (apps/paths don't exist on Linux) +if [ "$OS" = "Darwin" ]; then + sym hammerspoon .hammerspoon + sym vscode/code_settings.json .vscode/settings.json + sym zed/settings.json .config/zed/settings.json + sym zed/keymap.json .config/zed/keymap.json + sym ghostty/config Library/Application\ Support/com.mitchellh.ghostty/config -"$DOTFILES/claude/sync-settings.sh" -"$DOTFILES/codex/sync-config.sh" + brew bundle install +fi -function clone_pinned () { - url="$1"; dir="$2"; sha="$3" - if [ ! -d "$dir" ]; then - git clone --revision="$sha" "$url" "$dir" - else - # SHA may be missing locally — newer than the last fetch, or absent because - # the prior clone was shallow (--revision pulls only that one commit). - # Fetch it directly. - git -C "$dir" fetch origin "$sha" - git -C "$dir" checkout "$sha" - fi -} +# Sync portable agent settings after Homebrew has supplied jq, yq, and Codex on +# macOS. On Linux, run each sync when its dependencies are already available. +if command -v jq >/dev/null 2>&1; then + "$DOTFILES/claude/sync-settings.sh" +else + echo "WARN: jq is unavailable — skipping Claude settings sync" >&2 +fi +if command -v yq >/dev/null 2>&1 && command -v codex >/dev/null 2>&1; then + "$DOTFILES/codex/sync-config.sh" +else + echo "WARN: yq or codex is unavailable — skipping Codex settings sync" >&2 +fi # Install oh-my-zsh (clone repo directly; install.sh is just `git clone` once # its zshrc/runzsh/chsh side-effects are disabled) @@ -79,17 +109,41 @@ if [ ! -d "$HOME/.cargo" ]; then | sh -s -- -y --no-modify-path --default-toolchain 1.95.0 fi -# Install git-delta from a pinned GitHub release tarball (sidesteps brew bottle ABI drift) +# Install git-delta from a pinned GitHub release tarball (sidesteps brew bottle ABI drift). +# Tarball target and SHA256 are platform-specific. DELTA_VERSION=0.19.2 -DELTA_SHA256=9be36612a5a13e9e386dc498fb8e50dc87c72ee42b63db0ea05b32f99a72a69a DELTA_BIN="$HOME/.local/bin/delta" -if [ ! -x "$DELTA_BIN" ] || [ "$("$DELTA_BIN" --version 2>/dev/null | awk '{print $2}')" != "$DELTA_VERSION" ]; then + +case "$OS-$ARCH" in + Darwin-arm64) + DELTA_TARGET=aarch64-apple-darwin + DELTA_SHA256=9be36612a5a13e9e386dc498fb8e50dc87c72ee42b63db0ea05b32f99a72a69a + ;; + Linux-x86_64) + DELTA_TARGET=x86_64-unknown-linux-gnu + DELTA_SHA256=8e695c5f586a8c53d6c3b01be0b4a422ed218bfed2a56191caebe373a1c18ab2 + ;; + *) + echo "WARN: no delta build pinned for $OS-$ARCH — skipping delta install" >&2 + DELTA_TARGET="" + ;; +esac + +if [ -n "$DELTA_TARGET" ] && { [ ! -x "$DELTA_BIN" ] || [ "$("$DELTA_BIN" --version 2>/dev/null | awk '{print $2}')" != "$DELTA_VERSION" ]; }; then tmp=$(mktemp -d) - curl -fsSL "https://github.com/dandavison/delta/releases/download/${DELTA_VERSION}/delta-${DELTA_VERSION}-aarch64-apple-darwin.tar.gz" -o "$tmp/delta.tar.gz" - echo "${DELTA_SHA256} $tmp/delta.tar.gz" | shasum -a 256 -c - + curl -fsSL "https://github.com/dandavison/delta/releases/download/${DELTA_VERSION}/delta-${DELTA_VERSION}-${DELTA_TARGET}.tar.gz" -o "$tmp/delta.tar.gz" + if [ -n "$DELTA_SHA256" ]; then + if command -v sha256sum >/dev/null; then + echo "${DELTA_SHA256} $tmp/delta.tar.gz" | sha256sum -c - + else + echo "${DELTA_SHA256} $tmp/delta.tar.gz" | shasum -a 256 -c - + fi + else + echo "WARN: no DELTA_SHA256 pinned for $OS-$ARCH — integrity check skipped" >&2 + fi mkdir -p "$HOME/.local/bin" tar -xzf "$tmp/delta.tar.gz" -C "$tmp" - install -m 755 "$tmp/delta-${DELTA_VERSION}-aarch64-apple-darwin/delta" "$DELTA_BIN" + install -m 755 "$tmp/delta-${DELTA_VERSION}-${DELTA_TARGET}/delta" "$DELTA_BIN" rm -rf "$tmp" fi @@ -97,9 +151,32 @@ fi clone_pinned https://github.com/tmux-plugins/tpm "$HOME/.tmux/plugins/tpm" 7bdb7ca33c9cc6440a600202b50142f401b6fe21 # v3.1.0 clone_pinned https://github.com/erikw/tmux-powerline "$HOME/.tmux/plugins/tmux-powerline" 6079ace8d534a01d4d964b8b854b223f72edaf4b # v3.2.0 -# Install Packer (nvim plugin manager) and run PackerSync -clone_pinned https://github.com/wbthomason/packer.nvim "$HOME/.local/share/nvim/site/pack/packer/start/packer.nvim" ea0cc3c59f67c440c5ff0bbe4fb9420f4350b9a3 # 2023-08-24, matches plugins.lua pin +# Install neovim on Linux from a pinned upstream tarball (macOS gets it via Brewfile). +# AL2023 doesn't ship neovim in its default dnf repos. Lands in $HOME/.local so no +# root needed, and runs before the PackerSync block below so the plugin sync works. +NVIM_VERSION=0.9.5 +NVIM_SHA256=44ee395d9b5f8a14be8ec00d3b8ead34e18fe6461e40c9c8c50e6956d643b6ca +if [ "$OS" = "Linux" ] && [ "$ARCH" = "x86_64" ] && ! command -v nvim >/dev/null; then + tmp=$(mktemp -d) + curl -fsSL "https://github.com/neovim/neovim/releases/download/v${NVIM_VERSION}/nvim-linux64.tar.gz" -o "$tmp/nvim.tar.gz" + if command -v sha256sum >/dev/null; then + echo "${NVIM_SHA256} $tmp/nvim.tar.gz" | sha256sum -c - + else + echo "${NVIM_SHA256} $tmp/nvim.tar.gz" | shasum -a 256 -c - + fi + mkdir -p "$HOME/.local/share" "$HOME/.local/bin" + tar -xzf "$tmp/nvim.tar.gz" -C "$HOME/.local/share" + ln -sf "$HOME/.local/share/nvim-linux64/bin/nvim" "$HOME/.local/bin/nvim" + rm -rf "$tmp" + # Make nvim visible to the rest of this script (PackerSync below) + export PATH="$HOME/.local/bin:$PATH" +fi + +# Install Packer (nvim plugin manager) and run PackerSync — only if nvim is available +if command -v nvim >/dev/null; then + clone_pinned https://github.com/wbthomason/packer.nvim "$HOME/.local/share/nvim/site/pack/packer/start/packer.nvim" ea0cc3c59f67c440c5ff0bbe4fb9420f4350b9a3 # 2023-08-24, matches plugins.lua pin -nvim --headless -c 'autocmd User PackerComplete quitall' -c 'PackerSync' || true + nvim --headless -c 'autocmd User PackerComplete quitall' -c 'PackerSync' || true +fi echo "> Assimilation successful!" diff --git a/bashrc b/bashrc index 795a00c..8de032c 100644 --- a/bashrc +++ b/bashrc @@ -14,7 +14,9 @@ export LANG='en_US.UTF-8' alias config="/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME" alias vi=nvim alias vim=nvim -alias ls="lsd -al" +# lsd comes from the Brewfile on macOS; guard so a Linux box without it keeps +# a working `ls` +command -v lsd >/dev/null 2>&1 && alias ls="lsd -al" # Docker alias d='docker' @@ -75,7 +77,7 @@ alias kuc='kubectl config' # nvm export NVM_DIR="$HOME/.nvm" -[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" --no-use # Load nvm without auto-use (auto-use errors with "N/A" when no .nvmrc is set) [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion # PYTHONPATH @@ -104,7 +106,7 @@ alias dbtr="dbt run" alias cb="cargo build" alias cr="cargo run" alias ct="cargo test" -. "$HOME/.cargo/env" +[ -f "$HOME/.cargo/env" ] && . "$HOME/.cargo/env" # modal alias md="modal deploy" diff --git a/zed/settings.json b/zed/settings.json index b46a8f0..f3a1182 100644 --- a/zed/settings.json +++ b/zed/settings.json @@ -1,4 +1,7 @@ { + "project_panel": { + "dock": "left" + }, "cursor_blink": false, "terminal": { "cursor_shape": "bar" diff --git a/zshrc b/zshrc index b4cbca2..aea3ca7 100644 --- a/zshrc +++ b/zshrc @@ -47,13 +47,19 @@ else fi export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH" -source $(brew --prefix)/share/zsh-autosuggestions/zsh-autosuggestions.zsh + +# zsh-autosuggestions (installed via brew on macOS, may be missing elsewhere) +if command -v brew >/dev/null 2>&1; then + _zsh_autosug="$(brew --prefix)/share/zsh-autosuggestions/zsh-autosuggestions.zsh" + [ -f "$_zsh_autosug" ] && source "$_zsh_autosug" + unset _zsh_autosug +fi export NVM_DIR="$HOME/.nvm" -[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" --no-use # Load nvm without auto-use (auto-use errors with "N/A" when no .nvmrc is set) [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion -. "$HOME/.cargo/env" +[ -f "$HOME/.cargo/env" ] && . "$HOME/.cargo/env" alias snowsql=/Applications/SnowSQL.app/Contents/MacOS/snowsql # The next line updates PATH for the Google Cloud SDK. @@ -62,8 +68,10 @@ if [ -f "$HOME/Downloads/google-cloud-sdk/path.zsh.inc" ]; then . "$HOME/Downloa # The next line enables shell command completion for gcloud. if [ -f "$HOME/Downloads/google-cloud-sdk/completion.zsh.inc" ]; then . "$HOME/Downloads/google-cloud-sdk/completion.zsh.inc"; fi -# .zsh syntax highlighting -source /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh +# .zsh syntax highlighting (installed via brew on macOS, may be missing elsewhere) +if [ -f /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]; then + source /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh +fi # Prepend so pinned overrides in assimilate.sh (e.g. delta) win over brew bottles with ABI drift export PATH="$HOME/.local/bin:$PATH"