diff --git a/.gitconfig-darwin b/.gitconfig-darwin index 92380d2..2b8fa70 100644 --- a/.gitconfig-darwin +++ b/.gitconfig-darwin @@ -1,16 +1,16 @@ [user] - email = kincaidoneil@users.noreply.github.com - name = Kincaid O'Neil + email = kincaidoneil@users.noreply.github.com + name = Kincaid O'Neil signingkey = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA+62vYiqoYgGwJ2TC1CJUlqBfF3iBFwKHFwnVO08gFy [commit] - gpgsign = true + gpgsign = true [gpg] format = ssh [gpg "ssh"] program = /Applications/1Password.app/Contents/MacOS/op-ssh-sign [core] - editor = code --wait + editor = code --wait [init] - defaultBranch = main + defaultBranch = main [push] autoSetupRemote = true diff --git a/.gitconfig-linux b/.gitconfig-linux index d4356b9..e6a7e0b 100644 --- a/.gitconfig-linux +++ b/.gitconfig-linux @@ -1,14 +1,14 @@ [user] - email = kincaidoneil@users.noreply.github.com - name = Kincaid O'Neil - signingKey = DA3BAC238DFEC340740E4C2E16666E7D706EB0FF + email = kincaidoneil@users.noreply.github.com + name = Kincaid O'Neil + signingKey = DA3BAC238DFEC340740E4C2E16666E7D706EB0FF [commit] - gpgsign = true + gpgsign = true [gpg] - program = gpg + program = gpg [core] - editor = code --wait + editor = code --wait [init] - defaultBranch = main + defaultBranch = main [push] autoSetupRemote = true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..2013caf --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,44 @@ +name: Test Install + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + + - name: Remove pre-installed Node + run: | + # macOS + sudo rm -f /opt/homebrew/bin/node + sudo rm -f /opt/homebrew/bin/npm + sudo rm -f /opt/homebrew/bin/npx + # Linux + sudo rm -f /usr/local/bin/n + sudo rm -f /usr/local/bin/node + sudo rm -f /usr/local/bin/npm + sudo rm -f /usr/local/bin/npx + + - name: Run install script + run: bash install.sh + + - name: Verify installations + shell: zsh {0} + run: | + # Check dotfiles are symlinked + test -L ~/.zshrc + test -L ~/.gitconfig + + # Check key commands exist + command -v brew + command -v node + command -v cargo + command -v zsh diff --git a/.zshrc b/.zshrc index 2fc3336..3aa5129 100644 --- a/.zshrc +++ b/.zshrc @@ -2,27 +2,21 @@ export N_PREFIX="$HOME/n"; [[ :$PATH: == *":$N_PREFIX/bin:"* ]] || PATH+=":$N_PREFIX/bin" # Bun completions -[ -s "/Users/kincaid/.bun/_bun" ] && source "/Users/kincaid/.bun/_bun" +[ -s "$HOME/.bun/_bun" ] && source "$HOME/.bun/_bun" # Bun export BUN_INSTALL="$HOME/.bun" export PATH="$BUN_INSTALL/bin:$PATH" -# pnpm -export PNPM_HOME="/Users/kincaid/Library/pnpm" -case ":$PATH:" in - *":$PNPM_HOME:"*) ;; - *) export PATH="$PNPM_HOME:$PATH" ;; -esac - # Claude Code and other things export PATH="$HOME/.local/bin:$PATH" # Homebrew (only if directory exists/on Darwin) -[ -d "/opt/homebrew" ] && eval "$(/opt/homebrew/bin/brew shellenv)" - -# Make OpenSSL available in PATH on Apple Silicon -[ -d "/opt/homebrew" ] && export PATH="/opt/homebrew/opt/openssl@3/bin:$PATH" +if [ -d "/opt/homebrew" ]; then + eval "$(/opt/homebrew/bin/brew shellenv)" + # Make OpenSSL available in PATH on Apple Silicon + export PATH="/opt/homebrew/opt/openssl@3/bin:$PATH" +fi # Config a better version of ls and tree view alias ls="eza -lhmua --group-directories-first" @@ -35,19 +29,13 @@ bindkey "^[[1;5D" backward-word bindkey '^H' backward-kill-word bindkey '5~' kill-word -# Export helper to stop and remove all running Docker containers +# Clean up Docker containers, images, networks, and volumes function docker-clean { - running_containers=$(docker ps -q) - if [[ -z "$running_containers" ]] - then - echo "No Docker containers running." - else - docker stop $(docker ps -q) - docker rm $(docker ps -a -q) - fi + docker stop $(docker ps -q) 2>/dev/null || true + docker system prune -af --volumes } -# Zi initializaiton +# Zi initialization source "$HOME/.zi/bin/zi.zsh" autoload -Uz _zi (( ${+_comps} )) && _comps[zi]=_zi diff --git a/README.md b/README.md index 541b555..0296745 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,39 @@ -## Kincaid's Config +## Kincaid's Dotfiles ### Install -To install on a fresh Linux box, create new user account as `root`: +#### On macOS or Linux + +Run the install script to set up packages and dotfiles: ```bash -source <(curl -s https://raw.githubusercontent.com/kincaidoneil/dotfiles/main/add-user.sh) +curl -s https://raw.githubusercontent.com/kincaidoneil/dotfiles/main/install.sh | bash ``` -On Linux or macOS, run install script for packages and settings: +This will: +- Install Homebrew (if not present) +- Install all development tools and runtimes +- Symlink dotfiles (`.zshrc`, `.gitconfig`) +- Configure Zsh with plugins + +#### On a Fresh Linux Server (as root) + +To create a new user account with sudo access: ```bash -curl -s https://raw.githubusercontent.com/kincaidoneil/dotfiles/main/install.sh | bash -s +curl -s https://raw.githubusercontent.com/kincaidoneil/dotfiles/main/add-user.sh | bash ``` -Installs packages with `apt` on Debian, or `brew` on macOS. +Then switch to the new user and run the install script above. + +### Authentication Use 1Password to [manage SSH keys](https://developer.1password.com/docs/ssh/) and configure [SSH commit signing](https://developer.1password.com/docs/ssh/git-commit-signing). -#### Configure GPG commit signing +
+Alternative: Manual GPG/SSH Setup (Legacy) -_(Skip if using 1Password and/or SSH commit signing.)_ +#### Configure GPG Commit Signing Import GPG secret key: @@ -28,7 +41,7 @@ Import GPG secret key: gpg --import [PATH] ``` -If the key is expired, GPG may cryptically fail to sign with a `No secret key` error. To extend it: +If the key is expired, extend it: ```bash gpg --edit-key [KEY_ID] @@ -37,11 +50,9 @@ gpg --edit-key [KEY_ID] > save ``` -Use an [older revision](https://github.com/kincaidoneil/dotfiles/blob/315dbe3b078480ced80b398e016c152980369c18/.gitconfig-darwin) of `.gitconfig-[PLATFORM]` so Git and GPG place nice, and add the relevant signing key. +You'll need to use an [older revision](https://github.com/kincaidoneil/dotfiles/blob/315dbe3b078480ced80b398e016c152980369c18/.gitconfig-darwin) of `.gitconfig-[PLATFORM]` and manually install GPG tools (`brew install gpg2 pinentry-mac`). -#### Generate new SSH key - -_(Prefer using 1Password to generate new keys.)_ +#### Generate New SSH Key ```bash ssh-keygen -o -a 100 -t ed25519 -f ~/.ssh/id_ed25519 @@ -49,13 +60,13 @@ eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519 ``` -#### Add SSH pubkey to remote +#### Add SSH Pubkey to Remote Server -Add `~/.ssh/id_ed25519.pub` on client on a newline in the `~/.ssh/authorized_keys` file on the server. Refer [here](https://cryptsus.com/blog/how-to-secure-your-ssh-server-with-public-key-elliptic-curve-ed25519-crypto.html) for more background. +Add `~/.ssh/id_ed25519.pub` contents to `~/.ssh/authorized_keys` on the remote server. See [this guide](https://cryptsus.com/blog/how-to-secure-your-ssh-server-with-public-key-elliptic-curve-ed25519-crypto.html) for more details. -#### Add SSH config +#### Add SSH Config -To simplify connecting, add the server as an entry in the `~/.ssh/config` file on the client: +Simplify SSH connections by adding entries to `~/.ssh/config`: ``` Host @@ -64,16 +75,4 @@ Host UseKeychain yes ``` -### Cleanup - -```bash -cd ~ -rm -rf \ - n \ - .cargo \ - .rustup \ - .npm \ - .zi \ - .zshrc \ - .gitconfig -``` +
diff --git a/install.sh b/install.sh index 389a74f..af4bb87 100755 --- a/install.sh +++ b/install.sh @@ -3,148 +3,159 @@ set -e # Exit immediately when a command fails set -o pipefail # Pipes should also fail immediately -platform=$(uname -s | tr '[:upper:]' '[:lower:]') +# Ensure script is not run as root +if [ "$EUID" -eq 0 ]; then + echo "ERROR: Do not run this script as root or with sudo" + echo "The script will prompt for sudo password when needed" + exit 1 +fi -# Only install dependencies if not running in GitHub Codespaces -if [ ! "$CODESPACES" = true ] ; then - # Install Homebrew on both Mac & Linux +# Verify user has sudo access +if ! sudo -n true 2>/dev/null; then + echo "This script requires sudo access. You may be prompted for your password." + sudo -v || { + echo "ERROR: Unable to obtain sudo access" + exit 1 + } +fi - echo "Installing Homebrew..." - echo +platform=$(uname -s | tr '[:upper:]' '[:lower:]') - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +# Install Homebrew on both Mac & Linux +echo "Installing Homebrew..." +echo - if [ "$platform" = linux ] ; then - echo "Upgrading..." - echo +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || { + echo "ERROR: Homebrew installation failed - check output above" + exit 1 +} - sudo apt update || true - sudo apt -y dist-upgrade `# Explanation: https://www.techrepublic.com/article/how-to-tell-the-difference-between-apt-get-upgrade-apt-get-dist-upgrade-and-do-release-upgrade/` +if [ "$platform" = linux ] ; then + echo "Installing system utilities..." + echo - echo "Installing system utilities..." - echo + sudo apt update || true - sudo apt install -y \ - build-essential \ - cmake \ - coreutils \ - curl \ - docker.io `# Maintained by Debian. More info: https://stackoverflow.com/questions/45023363/what-is-docker-io-in-relation-to-docker-ce-and-docker-ee/57678382#57678382` \ - eza `# Replacement for ls` \ - git \ - gnupg2 \ - libssl-dev \ - ssh \ - sudo \ - unzip \ - wget \ - zsh + # Skip dist-upgrade in CI (slow, not needed for testing) + if [ "$CI" != "true" ]; then + sudo apt -y dist-upgrade + fi - echo "Configuring start-up services..." - echo + sudo apt install -y \ + build-essential \ + cmake \ + coreutils \ + curl \ + eza \ + git \ + gnupg2 \ + libssl-dev \ + ssh \ + sudo \ + unzip \ + wget \ + zsh + + # Skip Docker setup in CI (conflicts with pre-installed packages) + if [ "$CI" != "true" ]; then + sudo apt install -y docker.io + sudo gpasswd -a $USER docker + sudo systemctl start docker + sudo systemctl enable docker + fi - # Fix Docker permissions issue: https://superuser.com/questions/835696/how-solve-permission-problems-for-docker-in-ubuntu - sudo gpasswd -a kincaid docker + # Increase file watcher limit to maximum for VS Code + sudo sh -c 'echo "fs.inotify.max_user_watches=524288" >> /etc/sysctl.conf' - sudo systemctl start docker - sudo systemctl enable docker + # Set default shell to ZSH on Linux + sudo chsh -s /bin/zsh $USER +fi - # Increase file watcher limit to maximum for VS Code: https://code.visualstudio.com/docs/setup/linux#_visual-studio-code-is-unable-to-watch-for-file-changes-in-this-large-workspace-error-enospc - sudo sh -c 'echo "fs.inotify.max_user_watches=524288" >> /etc/sysctl.conf' +if [ "$platform" = darwin ] ; then + # Source Brew in this shell so brew command is available + eval "$(/opt/homebrew/bin/brew shellenv)" - # Set default shell to ZSH on Linux - sudo chsh -s /bin/zsh kincaid - fi + echo "Installing Homebrew packages..." + echo - if [ "$platform" = darwin ] ; then - # Source Brew in this shell - eval "$(/opt/homebrew/bin/brew shellenv)" - - echo "Installing Homebrew packages..." - echo - - brew update # Update Homebrew and all packages - - brew install \ - cmake \ - coreutils \ - curl \ - eza \ - gh `# GitHub CLI` \ - git \ - gpg2 `# gnupg2 is just an alias` \ - make \ - mkcert `# Tool to sign local certs for development` \ - ngrok/ngrok/ngrok \ - openssl `# Updated version of OpenSSL` \ - pinentry-mac \ - unzip \ - wget \ - zsh - - # Configure pinentry-mac so GPG key passwords are stored in macOS keychain - mkdir -p ~/.gnupg # GnuPG might not be created yet - echo "pinentry-program /opt/homebrew/bin/pinentry-mac" > ~/.gnupg/gpg-agent.conf - fi + brew install \ + cmake \ + coreutils \ + curl \ + eza \ + gh `# GitHub CLI` \ + git \ + make \ + mkcert `# Tool to sign local certs for development` \ + ngrok \ + openssl `# Updated version of OpenSSL` \ + unzip \ + wget \ + zsh +fi - echo "Installing Rust..." - echo +echo "Installing Rust..." +echo - # Install Rustup (Rust version management tool) which should auto install Rust & Cargo - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +# Install Rustup (Rust version management tool) which should auto install Rust & Cargo +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - # Only clone dotfiles after installing Git (that way, Git isn't a dependency of this script) - # In some environments, e.g. GitHub Codespaces, the repo will already be cloned - [ ! -d "$HOME/dotfiles" ] && git clone https://github.com/kincaidoneil/dotfiles +# Only clone dotfiles after installing Git (that way, Git isn't a dependency of this script) +if [ ! -d "$HOME/dotfiles" ]; then + git clone https://github.com/kincaidoneil/dotfiles "$HOME/dotfiles" +fi - echo "Installing Node.js..." - echo +echo "Installing Node.js..." +echo - # Install LTS and latest versions of Node (-y accepts confirm prompt, -n prevents modifying .zshrc, which already references n) - curl -L https://git.io/n-install | bash -s -- -y -n lts latest +# Install LTS and latest versions of Node (-y accepts confirm prompt, -n prevents modifying .zshrc, which already references n) +curl -L https://bit.ly/n-install | bash -s -- -y -n lts latest - # Add Node & npm to path in this context - # (.bashrc can only be re-sourced from an interactive shell, but not from a script) - export N_PREFIX="$HOME/n"; [[ :$PATH: == *":$N_PREFIX/bin:"* ]] || PATH+=":$N_PREFIX/bin" +# Add Node & npm to path in this context +# (.bashrc can only be re-sourced from an interactive shell, but not from a script) +export N_PREFIX="$HOME/n"; [[ :$PATH: == *":$N_PREFIX/bin:"* ]] || PATH+=":$N_PREFIX/bin" - echo "Installing Bun..." - echo +echo "Installing Bun..." +echo - curl -fsSL https://bun.sh/install | bash +curl -fsSL https://bun.sh/install | bash - echo "Installing pkgx..." - echo +echo "Installing pkgx..." +echo - curl -Ssf https://pkgx.sh | sh +curl -Ssf https://pkgx.sh | sh - echo "Installing Claude Code..." - echo +echo "Installing Claude Code..." +echo - curl -fsSL https://claude.ai/install.sh | bash -fi +curl -fsSL https://claude.ai/install.sh | bash npm i -g \ corepack `# Support for other package managers via npm` \ - pnpm \ pure-prompt \ trash-cli +# Enable corepack to manage pnpm and yarn +corepack enable + # Clean up existing dotfiles *only* rm -f ~/.zshrc ~/.gitconfig # If dotfiles exists in home directory, use that; otherwise, use enclosing folder of the current script -[ -d "$HOME/dotfiles" ] \ - && dotfiles_dir=$HOME/dotfiles \ - || dotfiles_dir=$(dirname "$(readlink -f "$0")") +if [ -d "$HOME/dotfiles" ]; then + dotfiles_dir="$HOME/dotfiles" +else + dotfiles_dir=$(cd "$(dirname "$0")" && pwd) +fi -ln -s $dotfiles_dir/.zshrc ~/.zshrc -ln -s $dotfiles_dir/.gitconfig-$platform ~/.gitconfig +ln -sf "$dotfiles_dir/.zshrc" ~/.zshrc +ln -sf "$dotfiles_dir/.gitconfig-$platform" ~/.gitconfig echo "Installing ZSH..." echo # Install zi (Zsh plugin manager, Antibody got deprecated and doesn't support M1) -sh -c "$(curl -fsSL https://git.io/get-zi)" -- -i skip +sh -c "$(curl -fsSL get.zshell.dev)" -- -i skip -b main echo "Completed installation." echo