From dd53a65ed5b0650f0e45d86327255e8e7e9a975b Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Sun, 5 Jul 2026 11:56:50 -0400 Subject: [PATCH 01/42] Fix SSH signing: gitconfig quoting broke gpg.ssh.defaultKeyCommand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git's config parser consumes the double quotes and \n escapes in the old printf wrapper before the shell sees them, so the wrapper executed the ssh-add output as a command and emitted a bare 'key::' — every commit and tag failed with 'user.signingKey needs to be set for ssh signing'. git only takes the first line of defaultKeyCommand output, so a bare ssh-add -L does the job with no quoting at all. This commit is itself signed via the fixed command. --- gitconfig | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gitconfig b/gitconfig index 9e772a9..66ceecf 100644 --- a/gitconfig +++ b/gitconfig @@ -18,9 +18,13 @@ # is currently exposed (macOS system agent, 1Password, gpg-agent, etc.) # rather than hardcoding ~/.ssh/id_ed25519.pub. Works with hardware- # resident keys (YubiKey ed25519-sk, 1Password) where there may be no - # .pub file on disk. The `key::` prefix is what git's source explicitly - # looks for in defaultKeyCommand output. - defaultKeyCommand = sh -c 'printf "key::%s\n" "$(ssh-add -L | head -n1)"' + # .pub file on disk. git takes the first line of this command's output + # as the signing key, so a bare `ssh-add -L` is all that's needed — no + # shell wrapper. (The previous printf wrapper broke signing entirely: + # git's config parser consumes the quotes and `\n` escapes before the + # shell ever sees them, so the key from the command substitution was + # *executed* as a command and the output reduced to a bare "key::".) + defaultKeyCommand = ssh-add -L [commit] gpgsign = true From 149ade5aa120e7f601aa370639beb676d30f9ec0 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Sat, 16 May 2026 14:34:56 -0400 Subject: [PATCH 02/42] Make assimilate.sh cross-platform (macOS + Linux) Branches on `uname -s` to skip macOS-only steps on Linux: - Skip hammerspoon/ghostty/vscode/zed symlinks and `brew bundle install` - Source dotfiles bashrc from $HOME/.bashrc instead of symlinking (the EC2 user_data appends a secrets block; symlinking would write into the repo) - Pick delta tarball + SHA256 per OS/arch (Linux SHA marked TODO until first install on the EC2 box) - Only run `nvim PackerSync` if nvim is on PATH Enables running the install script on the EC2 dev box without breaking the existing macOS flow. --- assimilate.sh | 111 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 80 insertions(+), 31 deletions(-) diff --git a/assimilate.sh b/assimilate.sh index e445ec1..859f847 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="$PREFIX/dotfiles" BACKUPS="$PREFIX/backups" @@ -24,6 +27,19 @@ function sym () { ln -s "$src" "$dest" } +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 +} + if [ ! -e "$DOTFILES" ]; then echo "error: dotfiles/ needs to reside in $PREFIX" exit 1 @@ -31,35 +47,39 @@ 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 zed/keymap.json .config/zed/keymap.json +sym claude/statusline.sh .claude/statusline.sh + +# 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 -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 -} + brew bundle install +fi # Install oh-my-zsh (clone repo directly; install.sh is just `git clone` once # its zshrc/runzsh/chsh side-effects are disabled) @@ -74,17 +94,44 @@ 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 + # TODO: pin SHA256 after first install on the EC2 box. + # Compute on the install target with `sha256sum delta.tar.gz`, then replace + # the empty default below to enable integrity verification. + DELTA_SHA256="" + ;; + *) + 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 @@ -92,9 +139,11 @@ 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 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!" From 07a2c59a0cff439123e12c58ffddde6c89e6c6b8 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Sat, 16 May 2026 17:42:29 -0400 Subject: [PATCH 03/42] Drop ls=lsd alias lsd isn't installed on the EC2 dev box (no brew, dnf doesn't ship it), and the dotfiles flow doesn't have a good place to install it cross-platform without dragging in a several-minute cargo compile. Plain ls is fine. --- bashrc | 1 - 1 file changed, 1 deletion(-) diff --git a/bashrc b/bashrc index 795a00c..da2b1f7 100644 --- a/bashrc +++ b/bashrc @@ -14,7 +14,6 @@ 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" # Docker alias d='docker' From 5c1a4f6108f77f001b120a921b76d596315877e4 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Sat, 16 May 2026 17:45:30 -0400 Subject: [PATCH 04/42] Guard ~/.cargo/env source against missing file Matches the same pattern nvm uses two lines above. Sourcing an absent file errors out on shell startup, which is harmless on Mac (rust is installed) but breaks the prompt on systems without rust. --- bashrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bashrc b/bashrc index da2b1f7..fe8cb3b 100644 --- a/bashrc +++ b/bashrc @@ -103,7 +103,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" From 60d1f92b6873ae7bacf7767678eada1d5f67c458 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Sat, 16 May 2026 18:29:24 -0400 Subject: [PATCH 05/42] Install neovim on Linux from a pinned tarball AL2023 (and likely most non-Fedora Linuxes) don't have neovim in the default package repos. macOS gets it from the Brewfile. Drops nvim into $HOME/.local/bin so no root is needed. Placed above the PackerSync block so the plugin sync that depends on `command -v nvim` actually fires. --- assimilate.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/assimilate.sh b/assimilate.sh index 859f847..0ab1ce3 100755 --- a/assimilate.sh +++ b/assimilate.sh @@ -139,6 +139,22 @@ 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 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.10.4 +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" + # TODO: pin SHA256 after first install; compute on the install target with `sha256sum nvim.tar.gz`. + 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 From bc9f3eaaae8bb6c307b807b63e4b3f2f9381939e Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Sun, 17 May 2026 09:48:32 -0400 Subject: [PATCH 06/42] Pin neovim to 0.9.5 to match macOS version v0.10.4 (my earlier guess) doesn't exist as a published release. v0.9.5 is what's installed on the Mac, and v0.9.x predates the asset-rename (still uses `nvim-linux64.tar.gz`), so the existing extraction path stays correct. --- assimilate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assimilate.sh b/assimilate.sh index 0ab1ce3..9518c96 100755 --- a/assimilate.sh +++ b/assimilate.sh @@ -142,7 +142,7 @@ clone_pinned https://github.com/erikw/tmux-powerline "$HOME/.tmux/plugins/tmux-p # 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.10.4 +NVIM_VERSION=0.9.5 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" From 2c3483bac0122cb84fb1671f467538dfd637954d Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Sun, 17 May 2026 14:21:26 -0400 Subject: [PATCH 07/42] Guard brew-shipped zsh plugin sources and ~/.cargo/env zsh-autosuggestions, zsh-syntax-highlighting, and rust may be absent on boxes where the Brewfile hasn't been applied (Linux dev box) or where those brew packages aren't installed. Mirror the existing nvm/bun pattern: source only when the file exists. --- zshrc | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/zshrc b/zshrc index b4cbca2..7b9f31f 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/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" From 885daf583059ac5621fef6b5c76cb1a167cec6ae Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Sun, 17 May 2026 14:41:23 -0400 Subject: [PATCH 08/42] Source nvm with --no-use to suppress "N/A" auto-use errors Without an argument, sourcing nvm.sh triggers nvm_auto use, which tries to resolve a version (from .nvmrc or default-alias) and errors with "N/A: version N/A -> N/A is not yet installed" when neither is set. --no-use loads nvm without invoking auto-use; users still get the `nvm` function and can `nvm use ` explicitly when they want one. --- bashrc | 2 +- zshrc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bashrc b/bashrc index fe8cb3b..4db50a2 100644 --- a/bashrc +++ b/bashrc @@ -74,7 +74,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 diff --git a/zshrc b/zshrc index 7b9f31f..aea3ca7 100644 --- a/zshrc +++ b/zshrc @@ -56,7 +56,7 @@ if command -v brew >/dev/null 2>&1; then 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 [ -f "$HOME/.cargo/env" ] && . "$HOME/.cargo/env" From e9d241731c80730407733c809ec45c883fc41d51 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Mon, 18 May 2026 15:10:18 -0400 Subject: [PATCH 09/42] Set zed project panel on lhs --- zed/settings.json | 3 +++ 1 file changed, 3 insertions(+) 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" From cf9a1e3a2ed7434b4758fc14c058ff39f9b48c80 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Mon, 18 May 2026 15:10:46 -0400 Subject: [PATCH 10/42] Clean up Brewfile --- Brewfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Brewfile b/Brewfile index 91ef6d4..d0d62a3 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" From 4afa8a781f13be2ec72d8605f9f7bd55d0196eee Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Fri, 29 May 2026 14:43:49 -0400 Subject: [PATCH 11/42] Pin Linux SHA256 for delta and neovim tarballs --- assimilate.sh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/assimilate.sh b/assimilate.sh index 9518c96..aa0c946 100755 --- a/assimilate.sh +++ b/assimilate.sh @@ -106,10 +106,7 @@ case "$OS-$ARCH" in ;; Linux-x86_64) DELTA_TARGET=x86_64-unknown-linux-gnu - # TODO: pin SHA256 after first install on the EC2 box. - # Compute on the install target with `sha256sum delta.tar.gz`, then replace - # the empty default below to enable integrity verification. - DELTA_SHA256="" + DELTA_SHA256=8e695c5f586a8c53d6c3b01be0b4a422ed218bfed2a56191caebe373a1c18ab2 ;; *) echo "WARN: no delta build pinned for $OS-$ARCH — skipping delta install" >&2 @@ -143,10 +140,15 @@ clone_pinned https://github.com/erikw/tmux-powerline "$HOME/.tmux/plugins/tmux-p # 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" - # TODO: pin SHA256 after first install; compute on the install target with `sha256sum 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" From 6bf971bff92349b1433dc0fe8295c1df40319050 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Fri, 29 May 2026 14:51:49 -0400 Subject: [PATCH 12/42] Make clone_pinned work on git <2.49 (drop --revision) AL2023 on older/un-updated AMIs ships git 2.40, which lacks `git clone --revision`. Use git init + fetch --depth 1 + checkout instead, which pins the same single commit on any git version. --- assimilate.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/assimilate.sh b/assimilate.sh index aa0c946..d91c6a6 100755 --- a/assimilate.sh +++ b/assimilate.sh @@ -29,15 +29,15 @@ function sym () { 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 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" + 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 From 56bd4cfa5ad30122c6464629a4b937d83605cfff Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Mon, 6 Jul 2026 20:52:15 -0400 Subject: [PATCH 13/42] Keep lsd ls alias on machines that have lsd The cross-platform pass dropped the alias entirely so Linux boxes without lsd keep a working ls, but that also removed it on macOS where lsd is installed via the Brewfile. Guard on the binary instead. --- bashrc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bashrc b/bashrc index 4db50a2..8de032c 100644 --- a/bashrc +++ b/bashrc @@ -14,6 +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 +# 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' From a14197438e40db173f3cc6d774ba99849bd6675a Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Thu, 13 Aug 2026 21:44:50 -0400 Subject: [PATCH 14/42] ci: validate dotfiles installs on macOS and Linux --- .github/dependabot.yml | 6 ++ .github/workflows/ci.yml | 125 ++++++++++++++++++++++++++++++++ .gitignore | 10 +++ .pre-commit-config.yaml | 5 ++ Brewfile | 1 + assimilate.sh | 15 +++- bashrc | 3 + scripts/ci/verify-assimilate.sh | 45 ++++++++++++ 8 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .pre-commit-config.yaml create mode 100755 scripts/ci/verify-assimilate.sh diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ca79ca5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b20010a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,125 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + +permissions: {} + +concurrency: + group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + UV_MALWARE_CHECK: "1" + +jobs: + static-checks: + name: Static checks + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + steps: + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - name: Check shell syntax + run: | + bash -n assimilate.sh bashrc bash_profile claude/statusline.sh scripts/ci/verify-assimilate.sh + zsh -n zshrc + - name: Check Git configuration + run: git config --file gitconfig --list >/dev/null + - name: Check strict JSON + run: | + jq empty nvim/coc-settings.json + jq empty vscode/code_settings.json + jq empty zed/keymap.json + + pre-commit: + name: Pre-commit + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + contents: read + steps: + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - name: Install pre-commit + run: python3 -m pip install --user pre-commit==4.6.2 + - name: Run repository hooks + run: python3 -m pre_commit run --all-files --show-diff-on-failure + + actions-security: + name: Actions security + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + contents: read + steps: + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - name: Validate workflows with actionlint + run: go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 + - name: Audit workflows with zizmor + run: pipx run zizmor==1.29.0 . + + secret-history: + name: Secret history + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + contents: read + steps: + - name: Check out full history + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + persist-credentials: false + - name: Install pinned Gitleaks + env: + GOBIN: ${{ runner.temp }}/bin + run: go install github.com/zricethezav/gitleaks/v8@v8.30.1 + - name: Scan Git history + run: '"${RUNNER_TEMP}/bin/gitleaks" git --redact --verbose' + + install: + name: Install (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: + - macos-26 + - ubuntu-24.04 + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + permissions: + contents: read + steps: + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - name: Install dotfiles twice in an isolated home + env: + HOMEBREW_NO_ANALYTICS: "1" + run: | + test_home="${RUNNER_TEMP}/dotfiles-home" + mkdir -p "$test_home" + ln -s "$GITHUB_WORKSPACE" "$test_home/dotfiles" + + HOME="$test_home" ./assimilate.sh + HOME="$test_home" DOTFILES="$GITHUB_WORKSPACE" scripts/ci/verify-assimilate.sh + + HOME="$test_home" ./assimilate.sh + HOME="$test_home" DOTFILES="$GITHUB_WORKSPACE" scripts/ci/verify-assimilate.sh + + test -z "$(find "$test_home/backups" -mindepth 1 -maxdepth 1 -print -quit)" diff --git a/.gitignore b/.gitignore index d713c1d..b5f4f2b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,13 @@ nvim/plugin/ gh/ .claude .wrangler + +# Secrets and machine-local tool state +*.pem +*.key +.env +.env.* +credentials.json +*.secret +Brewfile.lock.json +homebrew/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0f37bf7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,5 @@ +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.30.1 + hooks: + - id: gitleaks diff --git a/Brewfile b/Brewfile index d0d62a3..c707d3a 100644 --- a/Brewfile +++ b/Brewfile @@ -12,6 +12,7 @@ brew "neovim" brew "mosh" brew "node" brew "postgresql@17" +brew "pre-commit" brew "tmux" brew "tree" brew "wget" diff --git a/assimilate.sh b/assimilate.sh index d91c6a6..44e2b50 100755 --- a/assimilate.sh +++ b/assimilate.sh @@ -16,9 +16,16 @@ function sym () { # Ensure parent directory exists mkdir -p "$(dirname "$dest")" + # An already-correct link is installed. Leaving it alone makes repeated + # assimilation safe and avoids filling the backup directory on every run. + if [ -L "$dest" ] && [ "$(readlink "$dest")" = "$src" ]; then + return + fi + # Save existing dotfiles (also matches dangling symlinks, where -e alone returns false) if [ -e "$dest" ] || [ -L "$dest" ]; then - backup="$BACKUPS/$(basename $dest)-$(date +%s)" + backup="$(mktemp -d "$BACKUPS/$(basename "$dest").XXXXXX")" + rmdir "$backup" mv "$dest" "$backup" echo "> Moved $dest to $backup" fi @@ -81,6 +88,12 @@ if [ "$OS" = "Darwin" ]; then brew bundle install fi +# Install repository security hooks when pre-commit is available (Homebrew +# supplies it on macOS). Linux users can install pre-commit independently. +if command -v pre-commit >/dev/null 2>&1; then + (cd "$DOTFILES" && pre-commit install --allow-missing-config) +fi + # Install oh-my-zsh (clone repo directly; install.sh is just `git clone` once # its zshrc/runzsh/chsh side-effects are disabled) clone_pinned https://github.com/ohmyzsh/ohmyzsh "$HOME/.oh-my-zsh" e7aa0c56e68348afefdd6af4c5bdb314a2bd6640 # 2026-04 master HEAD diff --git a/bashrc b/bashrc index 8de032c..1726056 100644 --- a/bashrc +++ b/bashrc @@ -7,6 +7,9 @@ export EDITOR='nvim' export TERM='xterm-256color' export LANG='en_US.UTF-8' +# Ask uv to check resolved packages against malicious-package advisories. +export UV_MALWARE_CHECK=1 + # ---- # Aliases diff --git a/scripts/ci/verify-assimilate.sh b/scripts/ci/verify-assimilate.sh new file mode 100755 index 0000000..efc6cc7 --- /dev/null +++ b/scripts/ci/verify-assimilate.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +set -euo pipefail + +: "${DOTFILES:?DOTFILES must point to the checked-out repository}" + +assert_link() { + local source="$DOTFILES/$1" + local target="$HOME/$2" + + test -L "$target" + test "$(readlink "$target")" = "$source" +} + +assert_link bash_profile .bash_profile +assert_link gitconfig .gitconfig +assert_link tmux.conf .tmux.conf +assert_link zshrc .zshrc +assert_link nvim .config/nvim +assert_link tmux-powerline/config.sh .config/tmux-powerline/config.sh +assert_link tmux-powerline/themes/theme.sh .config/tmux-powerline/themes/theme.sh +assert_link claude/statusline.sh .claude/statusline.sh + +if [ "$(uname -s)" = Darwin ]; then + assert_link bashrc .bashrc + assert_link hammerspoon .hammerspoon + assert_link vscode/code_settings.json .vscode/settings.json + assert_link zed/settings.json .config/zed/settings.json + assert_link zed/keymap.json .config/zed/keymap.json + assert_link ghostty/config 'Library/Application Support/com.mitchellh.ghostty/config' +else + test ! -L "$HOME/.bashrc" + grep -Fq 'dotfiles/bashrc' "$HOME/.bashrc" +fi + +test "$(HOME="$HOME" bash -c 'source "$HOME/.bashrc" >/dev/null 2>&1; printf %s "$UV_MALWARE_CHECK"')" = 1 +test "$(HOME="$HOME" zsh -c 'source "$HOME/.zshrc" >/dev/null 2>&1; printf %s "$UV_MALWARE_CHECK"')" = 1 +test "$("$HOME/.local/bin/delta" --version)" = "delta 0.19.2" +test -x "$HOME/.cargo/bin/rustc" +test -d "$HOME/.oh-my-zsh/.git" +test -d "$HOME/.tmux/plugins/tpm/.git" +test -d "$HOME/.tmux/plugins/tmux-powerline/.git" + +git -C "$DOTFILES" diff --exit-code +git -C "$DOTFILES" diff --cached --exit-code From 7876782199acab2b83744bc2e4d9dfdd6e27964f Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Thu, 13 Aug 2026 21:48:50 -0400 Subject: [PATCH 15/42] ci: fix hosted runner prerequisites --- .github/dependabot.yml | 2 ++ .github/workflows/ci.yml | 19 ++++++++++++++----- assimilate.sh | 2 ++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ca79ca5..9cbb9b7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,5 @@ updates: directory: / schedule: interval: weekly + cooldown: + default-days: 7 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b20010a..0b7de08 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,9 +25,13 @@ jobs: contents: read steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + - name: Install Zsh + run: | + sudo apt-get update + sudo apt-get install --yes zsh - name: Check shell syntax run: | bash -n assimilate.sh bashrc bash_profile claude/statusline.sh scripts/ci/verify-assimilate.sh @@ -48,7 +52,7 @@ jobs: contents: read steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Install pre-commit @@ -64,7 +68,7 @@ jobs: contents: read steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Validate workflows with actionlint @@ -80,7 +84,7 @@ jobs: contents: read steps: - name: Check out full history - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: false @@ -105,9 +109,14 @@ jobs: contents: read steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + - name: Install Linux test prerequisites + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install --yes zsh - name: Install dotfiles twice in an isolated home env: HOMEBREW_NO_ANALYTICS: "1" diff --git a/assimilate.sh b/assimilate.sh index 44e2b50..dd9462d 100755 --- a/assimilate.sh +++ b/assimilate.sh @@ -85,6 +85,8 @@ if [ "$OS" = "Darwin" ]; then sym zed/keymap.json .config/zed/keymap.json sym ghostty/config Library/Application\ Support/com.mitchellh.ghostty/config + # Homebrew 6 requires explicit trust before installing formulae from a tap. + brew trust hashicorp/tap brew bundle install fi From 45f8b098c5dfcd235567f2a02fe27701b777f415 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:05:56 -0400 Subject: [PATCH 16/42] ci: preserve hosted Homebrew trust --- .github/workflows/ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b7de08..275c204 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,6 +125,13 @@ jobs: mkdir -p "$test_home" ln -s "$GITHUB_WORKSPACE" "$test_home/dotfiles" + # GitHub's macOS image has trusted taps for its preinstalled tools. + # Preserve only that public tap metadata when isolating HOME. + if [ -f "$HOME/.homebrew/trust.json" ]; then + mkdir -p "$test_home/.homebrew" + cp "$HOME/.homebrew/trust.json" "$test_home/.homebrew/trust.json" + fi + HOME="$test_home" ./assimilate.sh HOME="$test_home" DOTFILES="$GITHUB_WORKSPACE" scripts/ci/verify-assimilate.sh From 9d3961e2cea1e66d7aabb1baf4279458a8798133 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:15:42 -0400 Subject: [PATCH 17/42] ci: trust hosted runner AWS tap --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 275c204..969bebe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,6 +131,9 @@ jobs: mkdir -p "$test_home/.homebrew" cp "$HOME/.homebrew/trust.json" "$test_home/.homebrew/trust.json" fi + if [ "$(uname -s)" = Darwin ]; then + HOME="$test_home" brew trust aws/tap + fi HOME="$test_home" ./assimilate.sh HOME="$test_home" DOTFILES="$GITHUB_WORKSPACE" scripts/ci/verify-assimilate.sh From b37b6491b069dc7995541a05a05f150d036cbfa4 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:18:17 -0400 Subject: [PATCH 18/42] ci: use hosted runner home for integration --- .github/workflows/ci.yml | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 969bebe..9f79854 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,28 +117,21 @@ jobs: run: | sudo apt-get update sudo apt-get install --yes zsh - - name: Install dotfiles twice in an isolated home + - name: Install dotfiles twice on hosted runner env: HOMEBREW_NO_ANALYTICS: "1" run: | - test_home="${RUNNER_TEMP}/dotfiles-home" - mkdir -p "$test_home" - ln -s "$GITHUB_WORKSPACE" "$test_home/dotfiles" + # The hosted VM and its home directory are discarded after this job. + # Keep the runner's package-manager state while honoring assimilate's + # documented requirement that the repository live at $HOME/dotfiles. + test ! -e "$HOME/dotfiles" + ln -s "$GITHUB_WORKSPACE" "$HOME/dotfiles" - # GitHub's macOS image has trusted taps for its preinstalled tools. - # Preserve only that public tap metadata when isolating HOME. - if [ -f "$HOME/.homebrew/trust.json" ]; then - mkdir -p "$test_home/.homebrew" - cp "$HOME/.homebrew/trust.json" "$test_home/.homebrew/trust.json" - fi - if [ "$(uname -s)" = Darwin ]; then - HOME="$test_home" brew trust aws/tap - fi + ./assimilate.sh + DOTFILES="$HOME/dotfiles" scripts/ci/verify-assimilate.sh + backup_count="$(find "$HOME/backups" -mindepth 1 -maxdepth 1 | wc -l | tr -d ' ')" - HOME="$test_home" ./assimilate.sh - HOME="$test_home" DOTFILES="$GITHUB_WORKSPACE" scripts/ci/verify-assimilate.sh + ./assimilate.sh + DOTFILES="$HOME/dotfiles" scripts/ci/verify-assimilate.sh - HOME="$test_home" ./assimilate.sh - HOME="$test_home" DOTFILES="$GITHUB_WORKSPACE" scripts/ci/verify-assimilate.sh - - test -z "$(find "$test_home/backups" -mindepth 1 -maxdepth 1 -print -quit)" + test "$(find "$HOME/backups" -mindepth 1 -maxdepth 1 | wc -l | tr -d ' ')" = "$backup_count" From 35093feb4516774a57ac2d39674e9d6f8bb0a458 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:36:20 -0400 Subject: [PATCH 19/42] ci: test clean VPS installs --- .github/workflows/ci.yml | 26 +++++++++- scripts/ci/run-vps-container.sh | 82 ++++++++++++++++++++++++++++++++ scripts/ci/test-vps-container.sh | 14 ++++++ scripts/ci/verify-assimilate.sh | 14 ++++++ 4 files changed, 135 insertions(+), 1 deletion(-) create mode 100755 scripts/ci/run-vps-container.sh create mode 100755 scripts/ci/test-vps-container.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f79854..8b19b89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,8 +34,10 @@ jobs: sudo apt-get install --yes zsh - name: Check shell syntax run: | - bash -n assimilate.sh bashrc bash_profile claude/statusline.sh scripts/ci/verify-assimilate.sh + bash -n assimilate.sh bashrc bash_profile claude/statusline.sh scripts/ci/*.sh zsh -n zshrc + - name: Test VPS container harness + run: scripts/ci/test-vps-container.sh - name: Check Git configuration run: git config --file gitconfig --list >/dev/null - name: Check strict JSON @@ -135,3 +137,25 @@ jobs: DOTFILES="$HOME/dotfiles" scripts/ci/verify-assimilate.sh test "$(find "$HOME/backups" -mindepth 1 -maxdepth 1 | wc -l | tr -d ' ')" = "$backup_count" + + vps-install: + name: VPS install (${{ matrix.target }}) + strategy: + fail-fast: false + matrix: + target: + - ubuntu + - amazon-linux + runs-on: ubuntu-24.04 + timeout-minutes: 60 + permissions: + contents: read + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install dotfiles twice in a minimal VPS image + env: + VPS_TARGET: ${{ matrix.target }} + run: scripts/ci/run-vps-container.sh run "$VPS_TARGET" diff --git a/scripts/ci/run-vps-container.sh b/scripts/ci/run-vps-container.sh new file mode 100755 index 0000000..e034f71 --- /dev/null +++ b/scripts/ci/run-vps-container.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +set -euo pipefail + +usage() { + echo "usage: $0 image|run ubuntu|amazon-linux" >&2 + exit 2 +} + +image_for() { + case "$1" in + ubuntu) + echo "ubuntu:24.04@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea" + ;; + amazon-linux) + echo "amazonlinux:2023@sha256:694092ae18877ed4e3cb9b643759ba95df1f12af12528fefa18f60f79d4c1568" + ;; + *) + echo "unsupported VPS target: $1" >&2 + return 2 + ;; + esac +} + +command="${1:-}" +target="${2:-}" +[ "$#" -eq 2 ] || usage + +image="$(image_for "$target")" + +if [ "$command" = image ]; then + echo "$image" + exit 0 +fi + +[ "$command" = run ] || usage + +repo_root="$(git rev-parse --show-toplevel)" + +docker run --rm \ + --platform linux/amd64 \ + --env VPS_TARGET="$target" \ + --volume "$repo_root:/workspace:ro" \ + "$image" \ + bash -s <<'CONTAINER' +set -euxo pipefail + +case "$VPS_TARGET" in + ubuntu) + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install --yes ca-certificates curl findutils git gzip passwd tar zsh + ;; + amazon-linux) + dnf install --assumeyes ca-certificates curl findutils git gzip shadow-utils tar util-linux zsh + ;; + *) + echo "unsupported VPS target: $VPS_TARGET" >&2 + exit 2 + ;; +esac + +useradd --create-home --shell /bin/bash dotfiles +cp -a /workspace /home/dotfiles/dotfiles +chown -R dotfiles:dotfiles /home/dotfiles/dotfiles + +runuser --user dotfiles -- env \ + HOME=/home/dotfiles \ + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ + bash -c ' + set -euo pipefail + cd "$HOME/dotfiles" + + ./assimilate.sh + DOTFILES="$HOME/dotfiles" scripts/ci/verify-assimilate.sh + backup_count="$(find "$HOME/backups" -mindepth 1 -maxdepth 1 | wc -l | tr -d " ")" + + ./assimilate.sh + DOTFILES="$HOME/dotfiles" scripts/ci/verify-assimilate.sh + test "$(find "$HOME/backups" -mindepth 1 -maxdepth 1 | wc -l | tr -d " ")" = "$backup_count" + ' +CONTAINER diff --git a/scripts/ci/test-vps-container.sh b/scripts/ci/test-vps-container.sh new file mode 100755 index 0000000..d2f0c14 --- /dev/null +++ b/scripts/ci/test-vps-container.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -euo pipefail + +runner="scripts/ci/run-vps-container.sh" + +test -x "$runner" +test "$("$runner" image ubuntu)" = "ubuntu:24.04@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea" +test "$("$runner" image amazon-linux)" = "amazonlinux:2023@sha256:694092ae18877ed4e3cb9b643759ba95df1f12af12528fefa18f60f79d4c1568" + +if "$runner" image alpine >/dev/null 2>&1; then + echo "unsupported VPS target unexpectedly succeeded" >&2 + exit 1 +fi diff --git a/scripts/ci/verify-assimilate.sh b/scripts/ci/verify-assimilate.sh index efc6cc7..540cc84 100755 --- a/scripts/ci/verify-assimilate.sh +++ b/scripts/ci/verify-assimilate.sh @@ -40,6 +40,20 @@ test -x "$HOME/.cargo/bin/rustc" test -d "$HOME/.oh-my-zsh/.git" test -d "$HOME/.tmux/plugins/tpm/.git" test -d "$HOME/.tmux/plugins/tmux-powerline/.git" +test -d "$HOME/.local/share/nvim/site/pack/packer/start/packer.nvim/.git" + +nvim_log="$(mktemp)" +if ! HOME="$HOME" nvim --headless -c 'quitall' >"$nvim_log" 2>&1; then + cat "$nvim_log" >&2 + rm -f "$nvim_log" + exit 1 +fi +if grep -Fq 'Error detected while processing' "$nvim_log"; then + cat "$nvim_log" >&2 + rm -f "$nvim_log" + exit 1 +fi +rm -f "$nvim_log" git -C "$DOTFILES" diff --exit-code git -C "$DOTFILES" diff --cached --exit-code From d7b027df4ced5473aa9dac156a505e42b1ba9361 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:38:51 -0400 Subject: [PATCH 20/42] ci: attach VPS container input --- scripts/ci/run-vps-container.sh | 2 +- scripts/ci/test-vps-container.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/ci/run-vps-container.sh b/scripts/ci/run-vps-container.sh index e034f71..e7c67c6 100755 --- a/scripts/ci/run-vps-container.sh +++ b/scripts/ci/run-vps-container.sh @@ -37,7 +37,7 @@ fi repo_root="$(git rev-parse --show-toplevel)" -docker run --rm \ +docker run --rm --interactive \ --platform linux/amd64 \ --env VPS_TARGET="$target" \ --volume "$repo_root:/workspace:ro" \ diff --git a/scripts/ci/test-vps-container.sh b/scripts/ci/test-vps-container.sh index d2f0c14..94003c9 100755 --- a/scripts/ci/test-vps-container.sh +++ b/scripts/ci/test-vps-container.sh @@ -7,6 +7,7 @@ runner="scripts/ci/run-vps-container.sh" test -x "$runner" test "$("$runner" image ubuntu)" = "ubuntu:24.04@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea" test "$("$runner" image amazon-linux)" = "amazonlinux:2023@sha256:694092ae18877ed4e3cb9b643759ba95df1f12af12528fefa18f60f79d4c1568" +grep -Fq -- '--interactive' "$runner" if "$runner" image alpine >/dev/null 2>&1; then echo "unsupported VPS target unexpectedly succeeded" >&2 From d4f5a5af159f56351a228af7ac2948b5e0d23f35 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:42:52 -0400 Subject: [PATCH 21/42] fix: support clean Linux bootstrap --- assimilate.sh | 20 ++++++++++++-------- scripts/ci/run-vps-container.sh | 2 +- scripts/ci/test-vps-container.sh | 5 +++++ scripts/ci/verify-assimilate.sh | 1 + 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/assimilate.sh b/assimilate.sh index dd9462d..4f79c47 100755 --- a/assimilate.sh +++ b/assimilate.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -set -ex +set -euxo pipefail OS="$(uname -s)" ARCH="$(uname -m)" @@ -103,7 +103,7 @@ clone_pinned https://github.com/ohmyzsh/ohmyzsh "$HOME/.oh-my-zsh" e7aa0c56e6834 # Install rust via rustup-init.sh pinned to a specific GitHub commit (immutable), # with rustc toolchain version locked if [ ! -d "$HOME/.cargo" ]; then - RUSTUP_SHA=e10ffbdbb807c47fdd208119de99e7baae3e0dfe # rustup 1.29.0 + RUSTUP_SHA=28d1352dbcb436d3111c3594b9e1588e94950464 # rustup 1.29.0 tag's commit curl --proto '=https' --tlsv1.2 -sSf \ "https://raw.githubusercontent.com/rust-lang/rustup/$RUSTUP_SHA/rustup-init.sh" \ | sh -s -- -y --no-modify-path --default-toolchain 1.95.0 @@ -154,11 +154,12 @@ clone_pinned https://github.com/erikw/tmux-powerline "$HOME/.tmux/plugins/tmux-p # 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 +NVIM_VERSION=0.12.4 +NVIM_SHA256=012bf3fcac5ade43914df3f174668bf64d05e049a4f032a388c027b1ebd78628 +NVIM_BIN="$HOME/.local/bin/nvim" +if [ "$OS" = "Linux" ] && [ "$ARCH" = "x86_64" ] && { [ ! -x "$NVIM_BIN" ] || [ "$("$NVIM_BIN" --version | head -n 1)" != "NVIM v${NVIM_VERSION}" ]; }; 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" + curl -fsSL "https://github.com/neovim/neovim/releases/download/v${NVIM_VERSION}/nvim-linux-x86_64.tar.gz" -o "$tmp/nvim.tar.gz" if command -v sha256sum >/dev/null; then echo "${NVIM_SHA256} $tmp/nvim.tar.gz" | sha256sum -c - else @@ -166,9 +167,12 @@ if [ "$OS" = "Linux" ] && [ "$ARCH" = "x86_64" ] && ! command -v nvim >/dev/null 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" + ln -sf "$HOME/.local/share/nvim-linux-x86_64/bin/nvim" "$NVIM_BIN" rm -rf "$tmp" - # Make nvim visible to the rest of this script (PackerSync below) +fi + +if [ "$OS" = "Linux" ] && [ "$ARCH" = "x86_64" ] && [ -x "$NVIM_BIN" ]; then + # Prefer the pinned nvim for PackerSync, even when the host has another version. export PATH="$HOME/.local/bin:$PATH" fi diff --git a/scripts/ci/run-vps-container.sh b/scripts/ci/run-vps-container.sh index e7c67c6..484b884 100755 --- a/scripts/ci/run-vps-container.sh +++ b/scripts/ci/run-vps-container.sh @@ -52,7 +52,7 @@ case "$VPS_TARGET" in apt-get install --yes ca-certificates curl findutils git gzip passwd tar zsh ;; amazon-linux) - dnf install --assumeyes ca-certificates curl findutils git gzip shadow-utils tar util-linux zsh + dnf install --assumeyes ca-certificates curl-minimal findutils git gzip shadow-utils tar util-linux zsh ;; *) echo "unsupported VPS target: $VPS_TARGET" >&2 diff --git a/scripts/ci/test-vps-container.sh b/scripts/ci/test-vps-container.sh index 94003c9..da94c26 100755 --- a/scripts/ci/test-vps-container.sh +++ b/scripts/ci/test-vps-container.sh @@ -8,6 +8,11 @@ test -x "$runner" test "$("$runner" image ubuntu)" = "ubuntu:24.04@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea" test "$("$runner" image amazon-linux)" = "amazonlinux:2023@sha256:694092ae18877ed4e3cb9b643759ba95df1f12af12528fefa18f60f79d4c1568" grep -Fq -- '--interactive' "$runner" +grep -Fq 'curl-minimal' "$runner" + +grep -Fq 'set -euxo pipefail' assimilate.sh +grep -Fq '28d1352dbcb436d3111c3594b9e1588e94950464' assimilate.sh +grep -Fq 'NVIM_VERSION=0.12.4' assimilate.sh if "$runner" image alpine >/dev/null 2>&1; then echo "unsupported VPS target unexpectedly succeeded" >&2 diff --git a/scripts/ci/verify-assimilate.sh b/scripts/ci/verify-assimilate.sh index 540cc84..642bcc8 100755 --- a/scripts/ci/verify-assimilate.sh +++ b/scripts/ci/verify-assimilate.sh @@ -3,6 +3,7 @@ set -euo pipefail : "${DOTFILES:?DOTFILES must point to the checked-out repository}" +export PATH="$HOME/.local/bin:$PATH" assert_link() { local source="$DOTFILES/$1" From 3e78d24964d599cc0379819db0a2c7cfa62174a1 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:44:57 -0400 Subject: [PATCH 22/42] test: verify interactive VPS shell --- scripts/ci/test-vps-container.sh | 2 ++ scripts/ci/verify-assimilate.sh | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/ci/test-vps-container.sh b/scripts/ci/test-vps-container.sh index da94c26..2229bd8 100755 --- a/scripts/ci/test-vps-container.sh +++ b/scripts/ci/test-vps-container.sh @@ -13,6 +13,8 @@ grep -Fq 'curl-minimal' "$runner" grep -Fq 'set -euxo pipefail' assimilate.sh grep -Fq '28d1352dbcb436d3111c3594b9e1588e94950464' assimilate.sh grep -Fq 'NVIM_VERSION=0.12.4' assimilate.sh +grep -Fq 'set -euxo pipefail' scripts/ci/verify-assimilate.sh +grep -Fq 'bash --noprofile --norc -ic' scripts/ci/verify-assimilate.sh if "$runner" image alpine >/dev/null 2>&1; then echo "unsupported VPS target unexpectedly succeeded" >&2 diff --git a/scripts/ci/verify-assimilate.sh b/scripts/ci/verify-assimilate.sh index 642bcc8..b3a61b6 100755 --- a/scripts/ci/verify-assimilate.sh +++ b/scripts/ci/verify-assimilate.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -set -euo pipefail +set -euxo pipefail : "${DOTFILES:?DOTFILES must point to the checked-out repository}" export PATH="$HOME/.local/bin:$PATH" @@ -34,7 +34,7 @@ else grep -Fq 'dotfiles/bashrc' "$HOME/.bashrc" fi -test "$(HOME="$HOME" bash -c 'source "$HOME/.bashrc" >/dev/null 2>&1; printf %s "$UV_MALWARE_CHECK"')" = 1 +test "$(HOME="$HOME" bash --noprofile --norc -ic 'printf %s "$UV_MALWARE_CHECK"' 2>/dev/null)" = 1 test "$(HOME="$HOME" zsh -c 'source "$HOME/.zshrc" >/dev/null 2>&1; printf %s "$UV_MALWARE_CHECK"')" = 1 test "$("$HOME/.local/bin/delta" --version)" = "delta 0.19.2" test -x "$HOME/.cargo/bin/rustc" From aa6cb5a2615fc81a5523816406e0163d1f4571f9 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:46:59 -0400 Subject: [PATCH 23/42] test: load VPS bashrc --- scripts/ci/test-vps-container.sh | 6 +++++- scripts/ci/verify-assimilate.sh | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/ci/test-vps-container.sh b/scripts/ci/test-vps-container.sh index 2229bd8..ba97ca3 100755 --- a/scripts/ci/test-vps-container.sh +++ b/scripts/ci/test-vps-container.sh @@ -14,7 +14,11 @@ grep -Fq 'set -euxo pipefail' assimilate.sh grep -Fq '28d1352dbcb436d3111c3594b9e1588e94950464' assimilate.sh grep -Fq 'NVIM_VERSION=0.12.4' assimilate.sh grep -Fq 'set -euxo pipefail' scripts/ci/verify-assimilate.sh -grep -Fq 'bash --noprofile --norc -ic' scripts/ci/verify-assimilate.sh +grep -Fq 'bash --noprofile -ic' scripts/ci/verify-assimilate.sh +if grep -Fq -- '--norc' scripts/ci/verify-assimilate.sh; then + echo "VPS shell verification must not disable .bashrc" >&2 + exit 1 +fi if "$runner" image alpine >/dev/null 2>&1; then echo "unsupported VPS target unexpectedly succeeded" >&2 diff --git a/scripts/ci/verify-assimilate.sh b/scripts/ci/verify-assimilate.sh index b3a61b6..28fcd32 100755 --- a/scripts/ci/verify-assimilate.sh +++ b/scripts/ci/verify-assimilate.sh @@ -34,7 +34,7 @@ else grep -Fq 'dotfiles/bashrc' "$HOME/.bashrc" fi -test "$(HOME="$HOME" bash --noprofile --norc -ic 'printf %s "$UV_MALWARE_CHECK"' 2>/dev/null)" = 1 +test "$(HOME="$HOME" bash --noprofile -ic 'printf %s "$UV_MALWARE_CHECK"' 2>/dev/null)" = 1 test "$(HOME="$HOME" zsh -c 'source "$HOME/.zshrc" >/dev/null 2>&1; printf %s "$UV_MALWARE_CHECK"')" = 1 test "$("$HOME/.local/bin/delta" --version)" = "delta 0.19.2" test -x "$HOME/.cargo/bin/rustc" From c61056e3e139922a8ddaca3e0460f1b42e54708d Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:48:56 -0400 Subject: [PATCH 24/42] test: verify interactive VPS zsh --- scripts/ci/test-vps-container.sh | 1 + scripts/ci/verify-assimilate.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/ci/test-vps-container.sh b/scripts/ci/test-vps-container.sh index ba97ca3..37ea4a8 100755 --- a/scripts/ci/test-vps-container.sh +++ b/scripts/ci/test-vps-container.sh @@ -15,6 +15,7 @@ grep -Fq '28d1352dbcb436d3111c3594b9e1588e94950464' assimilate.sh grep -Fq 'NVIM_VERSION=0.12.4' assimilate.sh grep -Fq 'set -euxo pipefail' scripts/ci/verify-assimilate.sh grep -Fq 'bash --noprofile -ic' scripts/ci/verify-assimilate.sh +grep -Fq "zsh -ic 'printf" scripts/ci/verify-assimilate.sh if grep -Fq -- '--norc' scripts/ci/verify-assimilate.sh; then echo "VPS shell verification must not disable .bashrc" >&2 exit 1 diff --git a/scripts/ci/verify-assimilate.sh b/scripts/ci/verify-assimilate.sh index 28fcd32..04006bd 100755 --- a/scripts/ci/verify-assimilate.sh +++ b/scripts/ci/verify-assimilate.sh @@ -35,7 +35,7 @@ else fi test "$(HOME="$HOME" bash --noprofile -ic 'printf %s "$UV_MALWARE_CHECK"' 2>/dev/null)" = 1 -test "$(HOME="$HOME" zsh -c 'source "$HOME/.zshrc" >/dev/null 2>&1; printf %s "$UV_MALWARE_CHECK"')" = 1 +test "$(HOME="$HOME" zsh -ic 'printf %s "$UV_MALWARE_CHECK"' 2>/dev/null)" = 1 test "$("$HOME/.local/bin/delta" --version)" = "delta 0.19.2" test -x "$HOME/.cargo/bin/rustc" test -d "$HOME/.oh-my-zsh/.git" From 7779525f4cc1b93e736cb023c89488f2761f0ee1 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Thu, 13 Aug 2026 23:11:45 -0400 Subject: [PATCH 25/42] test: ignore shell startup output --- scripts/ci/test-vps-container.sh | 4 ++-- scripts/ci/verify-assimilate.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/ci/test-vps-container.sh b/scripts/ci/test-vps-container.sh index 37ea4a8..8ed261a 100755 --- a/scripts/ci/test-vps-container.sh +++ b/scripts/ci/test-vps-container.sh @@ -14,8 +14,8 @@ grep -Fq 'set -euxo pipefail' assimilate.sh grep -Fq '28d1352dbcb436d3111c3594b9e1588e94950464' assimilate.sh grep -Fq 'NVIM_VERSION=0.12.4' assimilate.sh grep -Fq 'set -euxo pipefail' scripts/ci/verify-assimilate.sh -grep -Fq 'bash --noprofile -ic' scripts/ci/verify-assimilate.sh -grep -Fq "zsh -ic 'printf" scripts/ci/verify-assimilate.sh +grep -Fq "bash --noprofile -ic 'test \"\$UV_MALWARE_CHECK\" = 1'" scripts/ci/verify-assimilate.sh +grep -Fq "zsh -ic 'test \"\$UV_MALWARE_CHECK\" = 1'" scripts/ci/verify-assimilate.sh if grep -Fq -- '--norc' scripts/ci/verify-assimilate.sh; then echo "VPS shell verification must not disable .bashrc" >&2 exit 1 diff --git a/scripts/ci/verify-assimilate.sh b/scripts/ci/verify-assimilate.sh index 04006bd..610d9e1 100755 --- a/scripts/ci/verify-assimilate.sh +++ b/scripts/ci/verify-assimilate.sh @@ -34,8 +34,8 @@ else grep -Fq 'dotfiles/bashrc' "$HOME/.bashrc" fi -test "$(HOME="$HOME" bash --noprofile -ic 'printf %s "$UV_MALWARE_CHECK"' 2>/dev/null)" = 1 -test "$(HOME="$HOME" zsh -ic 'printf %s "$UV_MALWARE_CHECK"' 2>/dev/null)" = 1 +HOME="$HOME" bash --noprofile -ic 'test "$UV_MALWARE_CHECK" = 1' >/dev/null 2>&1 +HOME="$HOME" zsh -ic 'test "$UV_MALWARE_CHECK" = 1' >/dev/null 2>&1 test "$("$HOME/.local/bin/delta" --version)" = "delta 0.19.2" test -x "$HOME/.cargo/bin/rustc" test -d "$HOME/.oh-my-zsh/.git" From cbc3ee19363d10fab3ed58b07ed80be8965cf264 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Sat, 15 Aug 2026 08:46:47 -0400 Subject: [PATCH 26/42] feat: install pinned Google Cloud CLI --- assimilate.sh | 44 ++++++++++++++++++++++++++++++++++++++++++++ zshrc | 17 +++++++++++------ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/assimilate.sh b/assimilate.sh index 4f79c47..d071c51 100755 --- a/assimilate.sh +++ b/assimilate.sh @@ -147,6 +147,50 @@ if [ -n "$DELTA_TARGET" ] && { [ ! -x "$DELTA_BIN" ] || [ "$("$DELTA_BIN" --vers rm -rf "$tmp" fi +# Install a pinned Google Cloud CLI archive without modifying shell profiles. +# zshrc sources the SDK's path and completion files from this location. +GCLOUD_VERSION=577.0.0 +GCLOUD_DIR="$HOME/.local/share/google-cloud-sdk" + +if ! command -v gcloud >/dev/null 2>&1 && [ ! -x "$GCLOUD_DIR/bin/gcloud" ]; then + case "$OS-$ARCH" in + Darwin-arm64) + GCLOUD_TARGET=darwin-arm + GCLOUD_SHA256=5ed8f9176eca367c6849fea10fde8e27d9a7d00a221c67f895c3020b8cf45a42 + ;; + Darwin-x86_64) + GCLOUD_TARGET=darwin-x86_64 + GCLOUD_SHA256=6e54ae17d744fc8ca12ac69886c880bd2b164141179f6a8a9d3acc53e6b5d3e8 + ;; + Linux-aarch64|Linux-arm64) + GCLOUD_TARGET=linux-arm + GCLOUD_SHA256=dbac26bdf80d72b5d13538e3a215dcbfe2781edfd2d69723effbeef3839cffb8 + ;; + Linux-x86_64) + GCLOUD_TARGET=linux-x86_64 + GCLOUD_SHA256=0b32d330446ce7b0f57f253e7efab4636c18fb1f87a3ac31c6c3f2a2a697525e + ;; + *) + echo "WARN: no Google Cloud CLI archive pinned for $OS-$ARCH — skipping installation" >&2 + GCLOUD_TARGET="" + ;; + esac + + if [ -n "$GCLOUD_TARGET" ]; then + tmp=$(mktemp -d) + GCLOUD_ARCHIVE="google-cloud-cli-${GCLOUD_VERSION}-${GCLOUD_TARGET}.tar.gz" + curl -fsSL "https://storage.googleapis.com/cloud-sdk-release/$GCLOUD_ARCHIVE" -o "$tmp/$GCLOUD_ARCHIVE" + if command -v sha256sum >/dev/null; then + echo "${GCLOUD_SHA256} $tmp/$GCLOUD_ARCHIVE" | sha256sum -c - + else + echo "${GCLOUD_SHA256} $tmp/$GCLOUD_ARCHIVE" | shasum -a 256 -c - + fi + mkdir -p "$HOME/.local/share" + tar -xzf "$tmp/$GCLOUD_ARCHIVE" -C "$HOME/.local/share" + rm -rf "$tmp" + fi +fi + # Install tmux plugin manager and plugins declared in tmux.conf 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 diff --git a/zshrc b/zshrc index aea3ca7..634c578 100644 --- a/zshrc +++ b/zshrc @@ -62,12 +62,6 @@ export NVM_DIR="$HOME/.nvm" [ -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. -if [ -f "$HOME/Downloads/google-cloud-sdk/path.zsh.inc" ]; then . "$HOME/Downloads/google-cloud-sdk/path.zsh.inc"; fi - -# 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 (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 @@ -94,3 +88,14 @@ precmd_functions+=(_reset_cursor) # bun export BUN_INSTALL="$HOME/.bun" export PATH="$BUN_INSTALL/bin:$PATH" + +# Load the Google Cloud SDK installed by assimilate.sh, or an existing SDK. +_gcloud_sdk_root="$HOME/.local/share/google-cloud-sdk" +if [ ! -d "$_gcloud_sdk_root" ] && (( $+commands[gcloud] )); then + _gcloud_sdk_root="${commands[gcloud]:A:h:h}" +elif [ ! -d "$_gcloud_sdk_root" ] && [ -d "$HOME/work/dev/google-cloud-sdk" ]; then + _gcloud_sdk_root="$HOME/work/dev/google-cloud-sdk" +fi +[ -f "$_gcloud_sdk_root/path.zsh.inc" ] && source "$_gcloud_sdk_root/path.zsh.inc" +[ -f "$_gcloud_sdk_root/completion.zsh.inc" ] && source "$_gcloud_sdk_root/completion.zsh.inc" +unset _gcloud_sdk_root From c16116ab56a5a55884d76f1a46648a3d83f15429 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Fri, 29 May 2026 15:04:31 -0400 Subject: [PATCH 27/42] Clean up bashrc: remove dead config, fix aliases, dedupe - Remove dead config bare-repo alias (symlink approach is used, not bare repo) - Remove 10 unused Mercurial (hg) aliases - Fix docker_rmi_dangling quoting (inner single quotes broke the alias) - Replace dead postgresql@14 PATH with version-agnostic $(brew --prefix postgresql@17)/bin - Remove dead sops PATH (~/Downloads/sops-3.7.2, no longer in Brewfile) - Remove hardcoded Python 3.11 framework PATH - Fix gdm/gdnm aliases: master -> main - Remove 'export TERM=xterm-256color' (let the terminal set terminfo) - Remove duplicate NVM init block (also in zshrc) - Remove duplicate cargo/env source (also in zshrc) - Remove EDITOR export (zshrc owns it with SSH-aware logic) --- bashrc | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/bashrc b/bashrc index 1726056..836a834 100644 --- a/bashrc +++ b/bashrc @@ -3,8 +3,6 @@ set -o vi -export EDITOR='nvim' -export TERM='xterm-256color' export LANG='en_US.UTF-8' # Ask uv to check resolved packages against malicious-package advisories. @@ -14,7 +12,6 @@ export UV_MALWARE_CHECK=1 # Aliases # general -alias config="/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME" alias vi=nvim alias vim=nvim # lsd comes from the Brewfile on macOS; guard so a Linux box without it keeps @@ -26,7 +23,7 @@ alias d='docker' alias dc='docker compose' alias dcup='docker compose up' alias dsp='docker system prune --all --force' -alias docker_rmi_dangling='docker rmi $(docker images -qa -f 'dangling=true') -f' +alias docker_rmi_dangling='docker rmi $(docker images -qa -f "dangling=true") -f' # python alias python='python3.11' @@ -55,22 +52,10 @@ alias gg='git grep' alias gl='git log --reverse -n 10' alias gpl='git pull' alias gp='git push' -alias gdm='git diff master' -alias gdnm='git diff --name-only origin/master' +alias gdm='git diff main' +alias gdnm='git diff --name-only origin/main' alias gcap='git checkout main && git pull' -# hg -alias hs='hg status' -alias hc='hg commit' -alias ha='hg amend' -alias hd='hg diff' -alias hl='hg log' -alias hu='hg update' -alias hp='hg prev' -alias hn='hg next' -alias ht='hg top' -alias hb='hg bottom' - # kube alias ku='kubectl' alias kuc='kubectl config' @@ -78,17 +63,6 @@ alias kuc='kubectl config' # --- # Applications -# nvm -export NVM_DIR="$HOME/.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 -export PATH="$PATH:/Library/Frameworks/Python.framework/Versions/3.11/bin" - -# sops -export PATH="$PATH:~/Downloads/sops-3.7.2" - # Homebrew # Intel Path export PATH="/usr/local/bin:${PATH}" @@ -97,7 +71,7 @@ export PATH="/usr/local/bin:${PATH}" export PATH="/opt/homebrew/bin:${PATH}" # Postgres -export PATH="/opt/homebrew/Cellar/postgresql@14/14.6_1/bin:${PATH}" +export PATH="$(brew --prefix postgresql@17)/bin:${PATH}" # dbt alias docs="dbt docs generate; dbt docs serve" @@ -109,7 +83,6 @@ alias dbtr="dbt run" alias cb="cargo build" alias cr="cargo run" alias ct="cargo test" -[ -f "$HOME/.cargo/env" ] && . "$HOME/.cargo/env" # modal alias md="modal deploy" From c7fbc8c2ae35eb7dfb5e2d62a1a59478f8a8e357 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Fri, 29 May 2026 15:06:09 -0400 Subject: [PATCH 28/42] Clean up zshrc: stop sourcing bashrc, drop OMZ git plugin, fix shell conflicts - Stop sourcing ~/.bashrc from zshrc (bash-specific syntax can break under zsh); inline the shared aliases/exports directly (extracted to shared.sh in follow-up) - Remove the oh-my-zsh git plugin (custom git aliases shadow every alias it provides) - Resolve set -o vi vs bindkey -e: drop 'set -o vi' from bashrc so zsh uses emacs line editing consistently (was silently overridden anyway) - Resolve brew node vs nvm: remove brew "node" from Brewfile so nvm manages Node versions (per-project .nvmrc), matching the active setup --- Brewfile | 1 - bashrc | 2 -- zshrc | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/Brewfile b/Brewfile index c707d3a..76cce2c 100644 --- a/Brewfile +++ b/Brewfile @@ -10,7 +10,6 @@ brew "lsd" brew "lua" brew "neovim" brew "mosh" -brew "node" brew "postgresql@17" brew "pre-commit" brew "tmux" diff --git a/bashrc b/bashrc index 836a834..432fa67 100644 --- a/bashrc +++ b/bashrc @@ -1,8 +1,6 @@ # ---- # Basics -set -o vi - export LANG='en_US.UTF-8' # Ask uv to check resolved packages against malicious-package advisories. diff --git a/zshrc b/zshrc index 634c578..251135e 100644 --- a/zshrc +++ b/zshrc @@ -1,5 +1,82 @@ # Basics -source ~/.bashrc +# bashrc is no longer sourced from zshrc (bash-specific syntax can break under zsh). +# The shared aliases/exports below are inlined here; a follow-up extracts them to shared.sh. + +export LANG='en_US.UTF-8' +export XDG_CONFIG_HOME="$HOME/dotfiles" +export GPG_TTY=$(tty) + +# Homebrew (Intel + ARM) PATH +export PATH="/usr/local/bin:${PATH}" +export PATH="/opt/homebrew/bin:${PATH}" +# Postgres +export PATH="$(brew --prefix postgresql@17)/bin:${PATH}" + +# Aliases — general +alias vi=nvim +alias vim=nvim +alias ls="lsd -al" +alias claude="$HOME/.local/bin/claude" + +# Aliases — Docker +alias d='docker' +alias dc='docker compose' +alias dcup='docker compose up' +alias dsp='docker system prune --all --force' +alias docker_rmi_dangling='docker rmi $(docker images -qa -f "dangling=true") -f' + +# Aliases — python +alias python='python3.11' +alias python3='python3.11' +alias pip='python3.11 -m pip' +alias pip3='pip' +alias grepy='grep -r --include \*.py' +alias greps='grep -r --include \*.sql' +alias jpn='jupyter notebook' +function ver { + pip list | grep $1 +} + +# Aliases — git +alias g='git status' +alias gs='git status' +alias gf='git fetch' +alias gm='git merge' +alias ga='git add' +alias gb='git branch' +alias gc='git commit' +alias gck='git checkout' +alias gckb='git checkout -b' +alias gd='git diff' +alias gg='git grep' +alias gl='git log --reverse -n 10' +alias gpl='git pull' +alias gp='git push' +alias gdm='git diff main' +alias gdnm='git diff --name-only origin/main' +alias gcap='git checkout main && git pull' + +# Aliases — kube +alias ku='kubectl' +alias kuc='kubectl config' + +# Aliases — dbt +alias docs="dbt docs generate; dbt docs serve" +alias dbtb="dbt build" +alias dbtc="dbt compile" +alias dbtr="dbt run" + +# Aliases — rust +alias cb="cargo build" +alias cr="cargo run" +alias ct="cargo test" + +# Aliases — modal +alias md="modal deploy" +alias mr="modal run" + +# Aliases — terraform +alias tf="terraform" export ZSH="$HOME/.oh-my-zsh" export UPDATE_ZSH_DAYS=13 @@ -25,9 +102,8 @@ HIST_STAMPS="mm/dd/yyyy" COMPLETION_WAITING_DOTS="false" # Plugins -plugins=( - git -) +# (OMZ git plugin removed — custom git aliases above shadow every alias it provides) +plugins=() bindkey -e bindkey "^[begin" backward-word From 7de53161ca015da8b6ac16f5e03d51ebbf6435be Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Sun, 5 Jul 2026 09:44:13 -0400 Subject: [PATCH 29/42] Fix comment: OMZ git plugin shadowed the custom aliases, not vice versa --- zshrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zshrc b/zshrc index 251135e..60c8323 100644 --- a/zshrc +++ b/zshrc @@ -102,7 +102,8 @@ HIST_STAMPS="mm/dd/yyyy" COMPLETION_WAITING_DOTS="false" # Plugins -# (OMZ git plugin removed — custom git aliases above shadow every alias it provides) +# (OMZ git plugin removed — it loads after the custom git aliases above and was +# shadowing them, e.g. its gl='git pull' clobbered gl='git log --reverse -n 10') plugins=() bindkey -e From 31087f43d3ce3ab4bc2cabbe767b979ccf5eac57 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Fri, 29 May 2026 15:08:08 -0400 Subject: [PATCH 30/42] Restructure shell config into shared.sh sourced by both shells - Extract shared aliases, exports, and PATH into shared.sh (single source of truth) - Source shared.sh (~/.shared.sh) from both bashrc and zshrc instead of sourcing bashrc from zshrc - Consolidate all static PATH entries (Homebrew, Postgres, yarn, bun, ~/.local/bin) into one block in shared.sh with a _prepend_path dedup helper, preventing PATH from ballooning in nested shells (e.g. tmux) - Symlink shared.sh -> ~/.shared.sh in assimilate.sh --- assimilate.sh | 1 + bashrc | 97 ++------------------------------------------- shared.sh | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++ zshrc | 91 ++---------------------------------------- 4 files changed, 116 insertions(+), 181 deletions(-) create mode 100644 shared.sh diff --git a/assimilate.sh b/assimilate.sh index d071c51..832fdb0 100755 --- a/assimilate.sh +++ b/assimilate.sh @@ -56,6 +56,7 @@ mkdir -p "$BACKUPS/vim_backups" # Shared symlinks (work on macOS and Linux) sym bash_profile .bash_profile +sym shared.sh .shared.sh sym gitconfig .gitconfig sym tmux.conf .tmux.conf sym zshrc .zshrc diff --git a/bashrc b/bashrc index 432fa67..4a3f82d 100644 --- a/bashrc +++ b/bashrc @@ -1,102 +1,13 @@ # ---- # Basics -export LANG='en_US.UTF-8' - -# Ask uv to check resolved packages against malicious-package advisories. -export UV_MALWARE_CHECK=1 +# Shared config (aliases, exports, consolidated PATH) used by both bash and zsh +[ -f "$HOME/.shared.sh" ] && . "$HOME/.shared.sh" # ---- -# Aliases - -# general -alias vi=nvim -alias vim=nvim -# 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' -alias dc='docker compose' -alias dcup='docker compose up' -alias dsp='docker system prune --all --force' -alias docker_rmi_dangling='docker rmi $(docker images -qa -f "dangling=true") -f' - -# python -alias python='python3.11' -alias python3='python3.11' -alias pip='python3.11 -m pip' -alias pip3='pip' -alias grepy='grep -r --include \*.py' -alias greps='grep -r --include \*.sql' -alias jpn='jupyter notebook' -function ver { - pip list | grep $1 -} - -# git -alias g='git status' -alias gs='git status' -alias gf='git fetch' -alias gm='git merge' -alias ga='git add' -alias gb='git branch' -alias gc='git commit' -alias gck='git checkout' -alias gckb='git checkout -b' -alias gd='git diff' -alias gg='git grep' -alias gl='git log --reverse -n 10' -alias gpl='git pull' -alias gp='git push' -alias gdm='git diff main' -alias gdnm='git diff --name-only origin/main' -alias gcap='git checkout main && git pull' - -# kube -alias ku='kubectl' -alias kuc='kubectl config' - -# --- -# Applications - -# Homebrew -# Intel Path -export PATH="/usr/local/bin:${PATH}" +# bash-specific -# ARM/M1 Path -export PATH="/opt/homebrew/bin:${PATH}" - -# Postgres -export PATH="$(brew --prefix postgresql@17)/bin:${PATH}" - -# dbt -alias docs="dbt docs generate; dbt docs serve" -alias dbtb="dbt build" -alias dbtc="dbt compile" -alias dbtr="dbt run" - -# rust -alias cb="cargo build" -alias cr="cargo run" -alias ct="cargo test" - -# modal -alias md="modal deploy" -alias mr="modal run" - -# GPG -export GPG_TTY=$(tty) - -# Terraform -alias tf="terraform" +# Terraform completion if [ -n "$BASH_VERSION" ]; then complete -C /opt/homebrew/bin/terraform terraform fi - -# Set $XDG_CONFIG_HOME for Zed and Ghostty -export XDG_CONFIG_HOME="$HOME/dotfiles" - -# Claude -alias claude="$HOME/.local/bin/claude" diff --git a/shared.sh b/shared.sh new file mode 100644 index 0000000..b990389 --- /dev/null +++ b/shared.sh @@ -0,0 +1,108 @@ +# shared.sh — configuration common to both bash and zsh. +# +# Sourced from both bashrc and zshrc (via ~/.shared.sh, symlinked by assimilate.sh) +# so there is a single source of truth for aliases, exports, and PATH. +# Keep this POSIX-sh compatible — it must parse under both shells. + +# ---- +# Exports + +export LANG='en_US.UTF-8' +export GPG_TTY=$(tty) +export UV_MALWARE_CHECK=1 +# $XDG_CONFIG_HOME drives Zed and Ghostty config discovery +export XDG_CONFIG_HOME="$HOME/dotfiles" + +# ---- +# PATH +# +# Single consolidated block with a dedup helper. Prepending through this guard +# keeps PATH from ballooning with duplicates in nested shells (e.g. tmux), where +# the rc files are re-sourced. Last prepend wins (ends up first on PATH), so +# pinned tools in ~/.local/bin take priority over brew bottles with ABI drift. + +_prepend_path() { + case ":$PATH:" in + *":$1:"*) ;; # already present — skip + *) PATH="$1:$PATH" ;; + esac +} + +_prepend_path "/usr/local/bin" # Homebrew (Intel) +_prepend_path "/opt/homebrew/bin" # Homebrew (ARM/M1) +_prepend_path "$(brew --prefix postgresql@17)/bin" # Postgres +_prepend_path "$HOME/.yarn/bin" # Yarn global bins +_prepend_path "$HOME/.config/yarn/global/node_modules/.bin" +export BUN_INSTALL="$HOME/.bun" +_prepend_path "$BUN_INSTALL/bin" # Bun +_prepend_path "$HOME/.local/bin" # pinned tools (delta, claude, uv) +export PATH + +# ---- +# Aliases + +# general +alias vi=nvim +alias vim=nvim +command -v lsd >/dev/null 2>&1 && alias ls="lsd -al" +alias claude="$HOME/.local/bin/claude" + +# Docker +alias d='docker' +alias dc='docker compose' +alias dcup='docker compose up' +alias dsp='docker system prune --all --force' +alias docker_rmi_dangling='docker rmi $(docker images -qa -f "dangling=true") -f' + +# python +alias python='python3.11' +alias python3='python3.11' +alias pip='python3.11 -m pip' +alias pip3='pip' +alias grepy='grep -r --include \*.py' +alias greps='grep -r --include \*.sql' +alias jpn='jupyter notebook' +ver() { + pip list | grep "$1" +} + +# git +alias g='git status' +alias gs='git status' +alias gf='git fetch' +alias gm='git merge' +alias ga='git add' +alias gb='git branch' +alias gc='git commit' +alias gck='git checkout' +alias gckb='git checkout -b' +alias gd='git diff' +alias gg='git grep' +alias gl='git log --reverse -n 10' +alias gpl='git pull' +alias gp='git push' +alias gdm='git diff main' +alias gdnm='git diff --name-only origin/main' +alias gcap='git checkout main && git pull' + +# kube +alias ku='kubectl' +alias kuc='kubectl config' + +# dbt +alias docs="dbt docs generate; dbt docs serve" +alias dbtb="dbt build" +alias dbtc="dbt compile" +alias dbtr="dbt run" + +# rust +alias cb="cargo build" +alias cr="cargo run" +alias ct="cargo test" + +# modal +alias md="modal deploy" +alias mr="modal run" + +# terraform +alias tf="terraform" diff --git a/zshrc b/zshrc index 60c8323..93f3b53 100644 --- a/zshrc +++ b/zshrc @@ -1,82 +1,6 @@ # Basics -# bashrc is no longer sourced from zshrc (bash-specific syntax can break under zsh). -# The shared aliases/exports below are inlined here; a follow-up extracts them to shared.sh. - -export LANG='en_US.UTF-8' -export XDG_CONFIG_HOME="$HOME/dotfiles" -export GPG_TTY=$(tty) - -# Homebrew (Intel + ARM) PATH -export PATH="/usr/local/bin:${PATH}" -export PATH="/opt/homebrew/bin:${PATH}" -# Postgres -export PATH="$(brew --prefix postgresql@17)/bin:${PATH}" - -# Aliases — general -alias vi=nvim -alias vim=nvim -alias ls="lsd -al" -alias claude="$HOME/.local/bin/claude" - -# Aliases — Docker -alias d='docker' -alias dc='docker compose' -alias dcup='docker compose up' -alias dsp='docker system prune --all --force' -alias docker_rmi_dangling='docker rmi $(docker images -qa -f "dangling=true") -f' - -# Aliases — python -alias python='python3.11' -alias python3='python3.11' -alias pip='python3.11 -m pip' -alias pip3='pip' -alias grepy='grep -r --include \*.py' -alias greps='grep -r --include \*.sql' -alias jpn='jupyter notebook' -function ver { - pip list | grep $1 -} - -# Aliases — git -alias g='git status' -alias gs='git status' -alias gf='git fetch' -alias gm='git merge' -alias ga='git add' -alias gb='git branch' -alias gc='git commit' -alias gck='git checkout' -alias gckb='git checkout -b' -alias gd='git diff' -alias gg='git grep' -alias gl='git log --reverse -n 10' -alias gpl='git pull' -alias gp='git push' -alias gdm='git diff main' -alias gdnm='git diff --name-only origin/main' -alias gcap='git checkout main && git pull' - -# Aliases — kube -alias ku='kubectl' -alias kuc='kubectl config' - -# Aliases — dbt -alias docs="dbt docs generate; dbt docs serve" -alias dbtb="dbt build" -alias dbtc="dbt compile" -alias dbtr="dbt run" - -# Aliases — rust -alias cb="cargo build" -alias cr="cargo run" -alias ct="cargo test" - -# Aliases — modal -alias md="modal deploy" -alias mr="modal run" - -# Aliases — terraform -alias tf="terraform" +# Shared config (aliases, exports, consolidated PATH) used by both bash and zsh +[ -f "$HOME/.shared.sh" ] && . "$HOME/.shared.sh" export ZSH="$HOME/.oh-my-zsh" export UPDATE_ZSH_DAYS=13 @@ -123,8 +47,6 @@ else export EDITOR='nvim' fi -export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH" - # 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" @@ -144,9 +66,6 @@ if [ -f /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh 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" - autoload -U +X bashcompinit && bashcompinit complete -o nospace -C /opt/homebrew/bin/terraform terraform @@ -159,13 +78,9 @@ _reset_cursor() { printf '\e[6 q' } precmd_functions+=(_reset_cursor) -# bun completions +# bun completions ($BUN_INSTALL and its PATH entry live in shared.sh) [ -s "$HOME/.bun/_bun" ] && source "$HOME/.bun/_bun" -# bun -export BUN_INSTALL="$HOME/.bun" -export PATH="$BUN_INSTALL/bin:$PATH" - # Load the Google Cloud SDK installed by assimilate.sh, or an existing SDK. _gcloud_sdk_root="$HOME/.local/share/google-cloud-sdk" if [ ! -d "$_gcloud_sdk_root" ] && (( $+commands[gcloud] )); then From 8a81d79c0a8a0675dbff6e8f4a662ad4b965198a Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Sun, 5 Jul 2026 09:44:54 -0400 Subject: [PATCH 31/42] Guard brew --prefix in shared.sh for hosts without Homebrew --- shared.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shared.sh b/shared.sh index b990389..5ef6018 100644 --- a/shared.sh +++ b/shared.sh @@ -30,7 +30,11 @@ _prepend_path() { _prepend_path "/usr/local/bin" # Homebrew (Intel) _prepend_path "/opt/homebrew/bin" # Homebrew (ARM/M1) -_prepend_path "$(brew --prefix postgresql@17)/bin" # Postgres +# Postgres — guarded so shells without Homebrew (e.g. the Linux dev box) don't +# error on every startup +if command -v brew >/dev/null 2>&1; then + _prepend_path "$(brew --prefix postgresql@17)/bin" +fi _prepend_path "$HOME/.yarn/bin" # Yarn global bins _prepend_path "$HOME/.config/yarn/global/node_modules/.bin" export BUN_INSTALL="$HOME/.bun" From 6e0e6334a9b293c19b79a749739708b6db30b7b7 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Fri, 29 May 2026 15:18:21 -0400 Subject: [PATCH 32/42] Migrate Neovim plugins from Packer to lazy.nvim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace Packer bootstrap with lazy.nvim bootstrap pinned to v11.17.5 (self-installs on first launch) - Convert all use{} specs to lazy {} specs: run -> build, requires -> dependencies - Set mapleader before lazy.setup so lazy 'keys' specs bind correctly - Add priority = 1000 + lazy = false to the gruvbox colorscheme - Remove the BufWritePost PackerCompile autocmd - Lazy-load: telescope (keys + :Telescope), fugitive (:Git/:G + gs), markdown-preview (ft = markdown) - Fix nvim-treesitter ensure_installed: 'help' -> 'vimdoc' (parser renamed) - Delete nvim/coc-settings.json (unused; config uses lsp-zero + Mason) - Remove nvim/plugin/ from .gitignore (Packer artifact dir; lazy doesn't use it) - assimilate.sh: drop the packer clone + PackerSync; lazy self-bootstraps and we trigger a headless 'Lazy\! sync' instead Notes: - lsp.lua already used 'ts_ls' (not 'tsserver'), so no rename was needed there. - Kept the vim.tbl_flatten shim in init.lua: the still-pinned old plugin versions (e.g. telescope 0.1.1) reference it; remove only after unpinning. - lazy-lock.json is generated on first interactive launch — run nvim, let lazy install, verify (:Lazy), then commit the lockfile. --- .gitignore | 1 - assimilate.sh | 11 +- nvim/coc-settings.json | 6 - nvim/lua/config/plugins.lua | 141 +++++++++++--------- nvim/lua/config/plugins/nvim-treesitter.lua | 2 +- 5 files changed, 86 insertions(+), 75 deletions(-) delete mode 100644 nvim/coc-settings.json diff --git a/.gitignore b/.gitignore index b5f4f2b..eeb7bc6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ local .DS_Store -nvim/plugin/ gh/ .claude .wrangler diff --git a/assimilate.sh b/assimilate.sh index 832fdb0..7b782db 100755 --- a/assimilate.sh +++ b/assimilate.sh @@ -198,7 +198,7 @@ clone_pinned https://github.com/erikw/tmux-powerline "$HOME/.tmux/plugins/tmux-p # 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. +# root needed, and runs before the lazy.nvim sync below so plugin setup works. NVIM_VERSION=0.12.4 NVIM_SHA256=012bf3fcac5ade43914df3f174668bf64d05e049a4f032a388c027b1ebd78628 NVIM_BIN="$HOME/.local/bin/nvim" @@ -217,15 +217,14 @@ if [ "$OS" = "Linux" ] && [ "$ARCH" = "x86_64" ] && { [ ! -x "$NVIM_BIN" ] || [ fi if [ "$OS" = "Linux" ] && [ "$ARCH" = "x86_64" ] && [ -x "$NVIM_BIN" ]; then - # Prefer the pinned nvim for PackerSync, even when the host has another version. + # Prefer the pinned nvim for plugin sync, even when the host has another version. export PATH="$HOME/.local/bin:$PATH" fi -# Install Packer (nvim plugin manager) and run PackerSync — only if nvim is available +# lazy.nvim self-bootstraps (clones itself, pinned in nvim/lua/config/plugins.lua) +# on first launch. Trigger a headless sync when 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 "+Lazy! sync" +qa || true fi echo "> Assimilation successful!" diff --git a/nvim/coc-settings.json b/nvim/coc-settings.json deleted file mode 100644 index c5d03b8..0000000 --- a/nvim/coc-settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "suggest.noselect": false, - "python.autoComplete.showAdvancedMembers": false, - "python.jediEnabled": true, - "python.linting.mypyEnabled": true -} diff --git a/nvim/lua/config/plugins.lua b/nvim/lua/config/plugins.lua index 3932def..e49c221 100644 --- a/nvim/lua/config/plugins.lua +++ b/nvim/lua/config/plugins.lua @@ -1,80 +1,96 @@ --- Automatically run :PackerCompile whenever plugins.lua is updated with an autocommand: -vim.api.nvim_create_autocmd('BufWritePost', { - group = vim.api.nvim_create_augroup('PACKER', { clear = true }), - pattern = 'plugins.lua', - command = 'source | PackerCompile', -}) +-- Leader must be set before lazy.nvim loads so that lazy `keys` specs below +-- bind against the intended leader (also set in remaps.lua). +vim.g.mapleader = " " +vim.g.maplocalleader = " " -return require('packer').startup(function(use) - -- Packer can manage itself - use { - 'wbthomason/packer.nvim', - commit = 'ea0cc3c', - } +-- Bootstrap lazy.nvim (pinned to a stable tag; it self-installs on first launch, +-- so no separate clone step is needed in assimilate.sh). +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not (vim.uv or vim.loop).fs_stat(lazypath) then + local out = vim.fn.system({ + "git", "clone", "--filter=blob:none", + "--branch=v11.17.5", + "https://github.com/folke/lazy.nvim.git", + lazypath, + }) + if vim.v.shell_error ~= 0 then + error("Error cloning lazy.nvim:\n" .. out) + end +end +vim.opt.rtp:prepend(lazypath) - -- Devicons - -- Required by nvim-tree, lualine, and bufferline - use { +require("lazy").setup({ + -- Devicons — required by nvim-tree, lualine, and bufferline + { 'nvim-tree/nvim-web-devicons', tag = 'nerd-v3.2-compat', - } + }, -- Nvim file tree - use { + { 'nvim-tree/nvim-tree.lua', commit = '5e4475d', + dependencies = { 'nvim-tree/nvim-web-devicons' }, config = function() require("config.plugins.nvim-tree") - end - } + end, + }, - -- Markdown preview - use({ + -- Markdown preview (load only for markdown files) + { "iamcco/markdown-preview.nvim", commit = 'a923f5f', - run = function() vim.fn["mkdp#util#install"]() end, - }) + ft = "markdown", + build = function() vim.fn["mkdp#util#install"]() end, + }, -- Syntax highlighting - use { + { 'nvim-treesitter/nvim-treesitter', tag = 'v0.10.0', - run = function() + build = function() local ts_update = require('nvim-treesitter.install').update({ with_sync = true }) ts_update() end, config = function() require("config.plugins.nvim-treesitter") - end - } + end, + }, - -- Fuzzyfinder - use { + -- Fuzzyfinder (load on its keys / :Telescope) + { 'nvim-telescope/telescope.nvim', tag = '0.1.1', - requires = { { 'nvim-lua/plenary.nvim' } }, + dependencies = { 'nvim-lua/plenary.nvim' }, + cmd = 'Telescope', + keys = { '', 'pf', 'ps', 'vh' }, config = function() require("config.plugins.telescope") - end - } + end, + }, - -- Git - use { + -- Git (load on :Git / gs) + { 'tpope/vim-fugitive', commit = '46eaf89', - } + cmd = { 'Git', 'G' }, + keys = { { 'gs', 'Git', desc = 'Git status' } }, + config = function() + require("config.plugins.fugitive") + end, + }, -- LSP - use { + { 'VonHeikemen/lsp-zero.nvim', branch = 'v3.x', - requires = { + dependencies = { -- LSP Support { 'neovim/nvim-lspconfig' }, -- Required { -- Optional 'williamboman/mason.nvim', - run = function() + build = function() pcall(vim.cmd, 'MasonUpdate') end, }, @@ -87,57 +103,60 @@ return require('packer').startup(function(use) }, config = function() require("config.plugins.lsp") - end - } + end, + }, - -- Gruvbox - use { + -- Gruvbox (colorscheme — load first, before other UI plugins) + { "ellisonleao/gruvbox.nvim", tag = '2.0.0', + priority = 1000, + lazy = false, config = function() require("config.plugins.colors") - end - } + end, + }, -- GitGutter - use { + { 'airblade/vim-gitgutter', commit = 'fe0e8a2', config = function() require("config.plugins.gitgutter") - end - } + end, + }, -- Lualine - use { + { 'nvim-lualine/lualine.nvim', commit = '2248ef2', + dependencies = { 'nvim-tree/nvim-web-devicons' }, config = function() require("config.plugins.lualine") - end - } + end, + }, -- Vertical indent lines - use { + { "lukas-reineke/indent-blankline.nvim", tag = 'v3.3.7', - } + }, -- bufferline - use { + { 'akinsho/bufferline.nvim', tag = "v4.9.1", - config = function () + dependencies = { 'nvim-tree/nvim-web-devicons' }, + config = function() require("config.plugins.bufferline") - end - } + end, + }, -- Comments - -- add this to your lua/plugins.lua, lua/plugins/init.lua, or the file you keep your other plugins: - use { + { 'numToStr/Comment.nvim', config = function() require('Comment').setup() - end - } -end) + end, + }, +}) diff --git a/nvim/lua/config/plugins/nvim-treesitter.lua b/nvim/lua/config/plugins/nvim-treesitter.lua index 978f385..bd091bc 100644 --- a/nvim/lua/config/plugins/nvim-treesitter.lua +++ b/nvim/lua/config/plugins/nvim-treesitter.lua @@ -1,6 +1,6 @@ require("nvim-treesitter").setup { ensure_installed = { - "help", + "vimdoc", "lua", "javascript", "python", From bad8a46ca474952e356d2fc829806186ce811171 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Sun, 5 Jul 2026 10:06:58 -0400 Subject: [PATCH 33/42] Track lazy-lock.json so transitive plugin state is reproducible Generated by the headless Lazy! sync in assimilate.sh; commits match the pins in plugins.lua and lock the plugins that only pin a branch (LuaSnip, nvim-cmp, mason, Comment.nvim, ...). --- nvim/lazy-lock.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 nvim/lazy-lock.json diff --git a/nvim/lazy-lock.json b/nvim/lazy-lock.json new file mode 100644 index 0000000..8001fbc --- /dev/null +++ b/nvim/lazy-lock.json @@ -0,0 +1,23 @@ +{ + "Comment.nvim": { "branch": "master", "commit": "e30b7f2008e52442154b66f7c519bfd2f1e32acb" }, + "LuaSnip": { "branch": "master", "commit": "0abc8f390b278c3b4aabc4c004ac8a088b65cf24" }, + "bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" }, + "cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" }, + "gruvbox.nvim": { "branch": "main", "commit": "61b0b3be2f0cfd521667403a0367298144d6c165" }, + "indent-blankline.nvim": { "branch": "master", "commit": "29be0919b91fb59eca9e90690d76014233392bef" }, + "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" }, + "lsp-zero.nvim": { "branch": "v3.x", "commit": "77550f2f6cbf0959ef1583d845661af075f3442b" }, + "lualine.nvim": { "branch": "master", "commit": "2248ef254d0a1488a72041cfb45ca9caada6d994" }, + "markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" }, + "mason-lspconfig.nvim": { "branch": "main", "commit": "47059d71b42d74b0a1e9f61c1d99d301039c3b5b" }, + "mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" }, + "nvim-cmp": { "branch": "main", "commit": "a1d504892f2bc56c2e79b65c6faded2fd21f3eca" }, + "nvim-lspconfig": { "branch": "master", "commit": "d224a1920728ba129880efc700d4a0180ac4ecbb" }, + "nvim-tree.lua": { "branch": "master", "commit": "5e4475d8bf7a3646164e01d9b65ef68369b17e3c" }, + "nvim-treesitter": { "branch": "main", "commit": "42fc28ba918343ebfd5565147a42a26580579482" }, + "nvim-web-devicons": { "branch": "master", "commit": "21417212f640a1dad28a1408f04468819848f5e7" }, + "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" }, + "telescope.nvim": { "branch": "master", "commit": "c1a2af0af69e80e14e6b226d3957a064cd080805" }, + "vim-fugitive": { "branch": "master", "commit": "46eaf8918b347906789df296143117774e827616" }, + "vim-gitgutter": { "branch": "main", "commit": "fe0e8a2630eef548e4122096e4e2241f42208fe3" } +} From 1e77e4f79ce06619ddf9926f1463e30e08207fa3 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Fri, 29 May 2026 15:10:36 -0400 Subject: [PATCH 34/42] Harden assimilate.sh: arch-aware delta, quoted $HOME, perms - delta install: detect arch with uname -m. Apple Silicon uses the pinned aarch64-apple-darwin tarball; Intel Macs fall back to 'brew install git-delta' (delta ships no x86_64-apple-darwin build, so there's no pinned tarball to verify) - bash_profile: quote $HOME in the .bashrc test/source (handles paths with spaces) - chmod 600 ~/.gitconfig and chmod 700 ~/.ssh (when present) after symlinking --- assimilate.sh | 24 +++++++++++++++--------- bash_profile | 4 ++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/assimilate.sh b/assimilate.sh index 7b782db..e4728e6 100755 --- a/assimilate.sh +++ b/assimilate.sh @@ -65,6 +65,10 @@ 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 +# Lock down sensitive symlink targets (chmod follows the symlink to the repo file). +chmod 600 "$HOME/.gitconfig" +[ -d "$HOME/.ssh" ] && chmod 700 "$HOME/.ssh" + # 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 @@ -110,8 +114,8 @@ 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). -# Tarball target and SHA256 are platform-specific. +# Install git-delta from a pinned GitHub release tarball when available. +# delta 0.19.2 has no Intel macOS archive, so that platform uses Homebrew. DELTA_VERSION=0.19.2 DELTA_BIN="$HOME/.local/bin/delta" @@ -124,6 +128,12 @@ case "$OS-$ARCH" in DELTA_TARGET=x86_64-unknown-linux-gnu DELTA_SHA256=8e695c5f586a8c53d6c3b01be0b4a422ed218bfed2a56191caebe373a1c18ab2 ;; + Darwin-x86_64) + DELTA_TARGET="" + if [ "$("$DELTA_BIN" --version 2>/dev/null | awk '{print $2}')" != "$DELTA_VERSION" ]; then + brew install git-delta + fi + ;; *) echo "WARN: no delta build pinned for $OS-$ARCH — skipping delta install" >&2 DELTA_TARGET="" @@ -133,14 +143,10 @@ 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}-${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 + if command -v sha256sum >/dev/null; then + echo "${DELTA_SHA256} $tmp/delta.tar.gz" | sha256sum -c - else - echo "WARN: no DELTA_SHA256 pinned for $OS-$ARCH — integrity check skipped" >&2 + echo "${DELTA_SHA256} $tmp/delta.tar.gz" | shasum -a 256 -c - fi mkdir -p "$HOME/.local/bin" tar -xzf "$tmp/delta.tar.gz" -C "$tmp" diff --git a/bash_profile b/bash_profile index ed0d107..cddefc9 100644 --- a/bash_profile +++ b/bash_profile @@ -1,3 +1,3 @@ -if [ -f $HOME/.bashrc ]; then - source $HOME/.bashrc +if [ -f "$HOME/.bashrc" ]; then + source "$HOME/.bashrc" fi From f51608ac2cca7817033fe66f7628eab617d0bf2e Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Fri, 29 May 2026 15:11:09 -0400 Subject: [PATCH 35/42] Add repo hygiene: secret gitignore patterns + gitleaks pre-commit - .gitignore: block *.pem, *.key, .env, .env.*, credentials.json, *.secret from ever being committed - Add .pre-commit-config.yaml with the gitleaks hook (pinned v8.30.1) for secret scanning before commit - Gitignore Brewfile.lock.json (records per-machine/arch bottle hashes; not portable, so not useful to track for reproducibility) --- .gitignore | 1 - .pre-commit-config.yaml | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index eeb7bc6..bf7f550 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ local gh/ .claude .wrangler - # Secrets and machine-local tool state *.pem *.key diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0f37bf7..eef9bf0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,9 @@ +# Pre-commit hooks for this dotfiles repo. +# Install once with: pre-commit install +# Run against all files: pre-commit run --all-files repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.30.1 hooks: + # Scans staged changes for secrets before they can be committed. - id: gitleaks From f4c22c377be15d184526efce708d2d7dd89e239e Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Mon, 6 Jul 2026 21:47:33 -0400 Subject: [PATCH 36/42] =?UTF-8?q?Ignore=20homebrew/=20=E2=80=94=20brew=20w?= =?UTF-8?q?rites=20its=20tap=20trust=20store=20into=20XDG=5FCONFIG=5FHOME?= =?UTF-8?q?=20(this=20repo)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index bf7f550..11fa49e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,7 @@ gh/ credentials.json *.secret Brewfile.lock.json + +# XDG_CONFIG_HOME points at this repo, so tools sometimes drop machine-local +# state here. Homebrew writes its tap trust store (homebrew/trust.json). homebrew/ From 6a54a8ba9573922c7b911909491a810a7ad29550 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Fri, 29 May 2026 15:11:35 -0400 Subject: [PATCH 37/42] Refresh checklist.md for current tooling - Remove steps now automated by assimilate.sh (oh-my-zsh, zsh plugins, brew bundle, Nerd Font, tpm, Packer) - Add Ghostty, Zed, and Claude Code install steps - Replace pinned Nerd Font v2.3.3 reference with a note that brew bundle installs it (cask font-hack-nerd-font) - Add a header noting assimilate.sh handles most setup automatically --- checklist.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/checklist.md b/checklist.md index 852ce84..e6892be 100644 --- a/checklist.md +++ b/checklist.md @@ -1,5 +1,10 @@ # Checklist +> **Most setup is automated by [`assimilate.sh`](./assimilate.sh).** It symlinks +> all dotfiles and installs oh-my-zsh, Homebrew packages (`brew bundle`), Rust, +> git-delta, tmux plugin manager + plugins, and bootstraps the Neovim plugin +> manager. The steps below are the manual bits that can't be scripted. + # Mac settings - General: Automatically Hide and Show the Menu Bar -> True - Keyboard: Key Repeat -> Fast @@ -12,14 +17,13 @@ - Spotlight: Set default shortcut to `cmd + shift + space` - Alfred: Set the default shortcut to `cmd + space` - Alfred Clipboard history: enable text + image + file lists -- Terminal: set default theme to Dark Background -- Install [ohmyzsh](https://github.com/ohmyzsh/ohmyzsh) -- Install zsh plugins - - [git](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/git) - - [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md) -- Install [Homebrew](https://brew.sh/) -- Install all packages via `brew bundle` -- Install Hack Nerd Font ([v2.3.3](https://github.com/ryanoasis/nerd-fonts/releases/tag/v2.3.3)) -- Install tmux plugin manager [tpm](https://github.com/tmux-plugins/tpm). Run `tmux source tmux.conf`. -- Install plugins with `I` -- Install [Packer](https://github.com/wbthomason/packer.nvim#quickstart) and run `PackerSync`. +- Install [Homebrew](https://brew.sh/), then run `./assimilate.sh` +- Install [Ghostty](https://ghostty.org/) (terminal; config is symlinked from `ghostty/`) +- Install [Zed](https://zed.dev/) (editor; `settings.json` + `keymap.json` symlinked from `zed/`) +- Install [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (`curl -fsSL https://claude.ai/install.sh | bash`) +- In tmux, install plugins with `I` + +> The Hack Nerd Font is installed automatically via `brew bundle` +> (`cask "font-hack-nerd-font"`) — no manual version pin needed. +> oh-my-zsh, zsh plugins, tmux plugin manager (tpm), and the Neovim plugin +> manager are all handled by `assimilate.sh`. From 5dbc10fbec1638935217dc85c9a1127f2240b923 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Fri, 29 May 2026 15:08:39 -0400 Subject: [PATCH 38/42] Remove empty Zed keymap entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the {"context": "Editor && vim_mode == insert"} entry that had no bindings field — it was incomplete/dead config doing nothing. --- zed/keymap.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/zed/keymap.json b/zed/keymap.json index 2606a56..50e0967 100644 --- a/zed/keymap.json +++ b/zed/keymap.json @@ -7,8 +7,5 @@ "ctrl-a j": "workspace::ActivatePaneDown", "ctrl-a k": "workspace::ActivatePaneUp" } - }, - { - "context": "Editor && vim_mode == insert" } ] From a13334d300a2b6b0e7328a9180ffdaacef193f12 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Fri, 29 May 2026 15:09:00 -0400 Subject: [PATCH 39/42] Add hardened SSH client config - Add ssh/config with secure defaults for Host *: HashKnownHosts, AddKeysToAgent, IdentitiesOnly, VisualHostKey, StrictHostKeyChecking ask - Add a github.com Host block using ~/.ssh/id_ed25519 - Symlink ssh/config -> ~/.ssh/config in assimilate.sh and enforce chmod 700 ~/.ssh + chmod 600 ~/.ssh/config (SSH rejects loose permissions) --- assimilate.sh | 4 +++- ssh/config | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 ssh/config diff --git a/assimilate.sh b/assimilate.sh index e4728e6..cc4e40d 100755 --- a/assimilate.sh +++ b/assimilate.sh @@ -64,10 +64,12 @@ sym nvim .config/nvim 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 ssh/config .ssh/config # Lock down sensitive symlink targets (chmod follows the symlink to the repo file). chmod 600 "$HOME/.gitconfig" -[ -d "$HOME/.ssh" ] && chmod 700 "$HOME/.ssh" +chmod 700 "$HOME/.ssh" +chmod 600 "$HOME/.ssh/config" # 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 diff --git a/ssh/config b/ssh/config new file mode 100644 index 0000000..ca62c29 --- /dev/null +++ b/ssh/config @@ -0,0 +1,20 @@ +# SSH client configuration (symlinked to ~/.ssh/config by assimilate.sh) + +Host * + # Hash hostnames in known_hosts so a leaked file doesn't reveal every + # server you've connected to. + HashKnownHosts yes + # Load a key into the agent on first use so you aren't re-prompted. + AddKeysToAgent yes + # Only offer keys explicitly configured for a host — don't spray every + # loaded key at every server (avoids leaking fingerprints / auth failures). + IdentitiesOnly yes + # Show an ASCII-art fingerprint on connect to help spot MITM on first use. + VisualHostKey yes + # Never silently auto-accept unknown host keys. + StrictHostKeyChecking ask + +Host github.com + HostName github.com + User git + IdentityFile ~/.ssh/id_ed25519 From a3babe59c6c928e34c579aca3ac59c54f9fd198a Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Sun, 5 Jul 2026 09:52:49 -0400 Subject: [PATCH 40/42] Support machine-local hosts via Include + keep macOS UseKeychain The tracked config replaces ~/.ssh/config wholesale, which dropped the existing machine-local Host entry and the UseKeychain setting. Include config.local (untracked; repo is public) preserves per-machine hosts, and IgnoreUnknown lets UseKeychain coexist with Linux ssh. --- ssh/config | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ssh/config b/ssh/config index ca62c29..f2a4dc7 100644 --- a/ssh/config +++ b/ssh/config @@ -1,9 +1,18 @@ # SSH client configuration (symlinked to ~/.ssh/config by assimilate.sh) +# Machine-local hosts live in ~/.ssh/config.local (not tracked — this repo is +# public). ssh silently skips a missing Include, and entries there are read +# first so they take precedence for their hosts. +Include config.local + Host * # Hash hostnames in known_hosts so a leaked file doesn't reveal every # server you've connected to. HashKnownHosts yes + # Store/read key passphrases from the macOS keychain. IgnoreUnknown keeps + # Linux ssh (which has no UseKeychain option) from erroring on it. + IgnoreUnknown UseKeychain + UseKeychain yes # Load a key into the agent on first use so you aren't re-prompted. AddKeysToAgent yes # Only offer keys explicitly configured for a host — don't spray every From 170233407abb2c8d8b6964c789cb6dd090febafe Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Fri, 29 May 2026 15:13:05 -0400 Subject: [PATCH 41/42] Manage Python with uv + 7-day rolling minimum release age - Add uv/uv.toml with exclude-newer = "7 days" (rolling supply-chain window: never resolve a package release younger than a week) and python-preference = "managed" so uv owns Python version management - Symlink uv/uv.toml -> ~/.config/uv/uv.toml in assimilate.sh (also discovered via $XDG_CONFIG_HOME) - Add brew "uv" to the Brewfile and run 'uv python install' during assimilate to install a uv-managed CPython on fresh machines --- Brewfile | 1 + assimilate.sh | 7 +++++++ uv/uv.toml | 16 ++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 uv/uv.toml diff --git a/Brewfile b/Brewfile index 76cce2c..a63d208 100644 --- a/Brewfile +++ b/Brewfile @@ -14,6 +14,7 @@ brew "postgresql@17" brew "pre-commit" brew "tmux" brew "tree" +brew "uv" brew "wget" brew "zsh-autosuggestions" brew "zsh-syntax-highlighting" diff --git a/assimilate.sh b/assimilate.sh index cc4e40d..b73e777 100755 --- a/assimilate.sh +++ b/assimilate.sh @@ -65,6 +65,7 @@ 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 ssh/config .ssh/config +sym uv/uv.toml .config/uv/uv.toml # Lock down sensitive symlink targets (chmod follows the symlink to the repo file). chmod 600 "$HOME/.gitconfig" @@ -97,6 +98,12 @@ if [ "$OS" = "Darwin" ]; then brew bundle install fi +# Let uv manage Python when it is available. The rolling release-age window in +# uv/uv.toml applies to package resolution, while this installs the interpreter. +if command -v uv >/dev/null 2>&1; then + uv python install +fi + # Install repository security hooks when pre-commit is available (Homebrew # supplies it on macOS). Linux users can install pre-commit independently. if command -v pre-commit >/dev/null 2>&1; then diff --git a/uv/uv.toml b/uv/uv.toml new file mode 100644 index 0000000..d7f534b --- /dev/null +++ b/uv/uv.toml @@ -0,0 +1,16 @@ +# uv configuration — https://docs.astral.sh/uv/reference/settings/ +# +# Symlinked to ~/.config/uv/uv.toml by assimilate.sh (and also discovered via +# $XDG_CONFIG_HOME, which points at this repo). Applies to every uv invocation: +# uv lock / uv sync / uv pip install / uv run. + +# Supply-chain hardening: never resolve a package release younger than 7 days. +# This is a *rolling* window (a duration, not a fixed date) that moves with the +# clock — most malicious/compromised uploads are detected and yanked within a +# week, so waiting buys time without much practical cost. +exclude-newer = "7 days" + +# Let uv own Python version management: prefer uv-managed CPython interpreters +# over system/pyenv ones. Install versions with `uv python install ` and +# pin per-project with a `.python-version` file. +python-preference = "managed" From d8822c20d5dc29de4e24ebfc1159e6e08780dad3 Mon Sep 17 00:00:00 2001 From: Alex Kan <29241719+akan72@users.noreply.github.com> Date: Fri, 29 May 2026 15:14:10 -0400 Subject: [PATCH 42/42] Turn off Claude Code attribution via assimilate.sh (append-only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an idempotent step to assimilate.sh that sets Claude Code's commit/PR attribution to empty in ~/.claude/settings.json. It only appends the "attribution" key when it isn't already present (via jq merge), so existing local settings are never overridden — matching the 'append if absent' intent. Makes the attribution-off behavior reproducible on new machines. --- assimilate.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/assimilate.sh b/assimilate.sh index b73e777..9c683ff 100755 --- a/assimilate.sh +++ b/assimilate.sh @@ -242,4 +242,15 @@ if command -v nvim >/dev/null; then nvim --headless "+Lazy! sync" +qa || true fi +# Turn off Claude Code commit/PR attribution. Appends the "attribution" key only +# if it isn't already set, so existing local settings are never overridden. +CLAUDE_SETTINGS="$HOME/.claude/settings.json" +mkdir -p "$HOME/.claude" +if [ ! -f "$CLAUDE_SETTINGS" ]; then + printf '{\n "attribution": { "commit": "", "pr": "" }\n}\n' > "$CLAUDE_SETTINGS" +elif ! jq -e 'has("attribution")' "$CLAUDE_SETTINGS" >/dev/null 2>&1; then + tmp=$(mktemp) + jq '. + {attribution: {commit: "", pr: ""}}' "$CLAUDE_SETTINGS" > "$tmp" && mv "$tmp" "$CLAUDE_SETTINGS" +fi + echo "> Assimilation successful!"