diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df23e47..91651b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,11 @@ concurrency: jobs: test: - runs-on: macos-latest + strategy: + fail-fast: false + matrix: + os: [macos-latest, ubuntu-latest] + runs-on: ${{ matrix.os }} timeout-minutes: 45 steps: @@ -34,16 +38,38 @@ jobs: echo "DOTFILES=$GITHUB_WORKSPACE" >> $GITHUB_ENV ln -sf "$GITHUB_WORKSPACE" "$HOME/.dotfiles" - - name: Cache Homebrew packages - if: github.event_name != 'schedule' && !(github.event_name == 'workflow_dispatch' && inputs.skip_cache) + - name: Install Homebrew (Linux) + if: runner.os == 'Linux' + run: | + if ! command -v brew >/dev/null 2>&1; then + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" + fi + echo "/home/linuxbrew/.linuxbrew/bin" >> "$GITHUB_PATH" + + - name: Cache Homebrew packages (macOS) + if: runner.os == 'macOS' && github.event_name != 'schedule' && !(github.event_name == 'workflow_dispatch' && inputs.skip_cache) uses: actions/cache@v4 with: path: | ~/Library/Caches/Homebrew /opt/homebrew/Cellar /opt/homebrew/Library/Taps - key: brew-${{ hashFiles('macos/Brewfile') }} - restore-keys: brew- + key: brew-macos-${{ hashFiles('brew/Brewfile.common', 'brew/Brewfile.darwin') }} + restore-keys: brew-macos- + + - name: Cache Homebrew packages (Linux) + if: runner.os == 'Linux' && github.event_name != 'schedule' && !(github.event_name == 'workflow_dispatch' && inputs.skip_cache) + uses: actions/cache@v4 + with: + # Only cache downloads, not the installed Cellar: the runner ships a + # preinstalled Linuxbrew, and restoring cached kegs on top of it + # leaves them present-but-unregistered, which breaks `brew bundle`'s + # upgrade step ("File exists" / "No such keg"). Caching downloads + # still skips re-fetching bottles while installing cleanly each run. + path: | + ~/.cache/Homebrew + key: brew-linux-v2-${{ hashFiles('brew/Brewfile.common') }} + restore-keys: brew-linux-v2- - name: Install bats-core run: | diff --git a/README.md b/README.md index 0f9b083..b1ace9f 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,16 @@ before reinstalling everything from scratch... 1. save/backup all important documents from non-iCloud directories 1. save all your work from apps which aren't synced through iCloud 1. export important data from your local databases -1. update [mackup](https://github.com/lra/mackup) to the latest version and run `mackup backup` +1. update [mackup](https://github.com/lra/mackup) to the latest version and run `mackup backup` *(macOS only)* ## Installation +The `./install` script detects the platform (`uname`) and runs the right +combination of configs, so the first four steps are the same everywhere. + +### macOS + ```bash # 1. Update macOS to the latest version with the App Store sudo softwareupdate -i -a @@ -31,7 +36,7 @@ chmod 600 # 3. Clone this repo to ~/.dotfiles cd ~; git clone git@github.com:marcaube/dotfiles.git .dotfiles -# 4. Run install.sh to start the installation +# 4. Run the install script cd ~/.dotfiles; ./install; cd - # 5. Login with your Apple ID, check iCloud Drive and let it sync for a while @@ -46,6 +51,28 @@ brew doctor shutdown -r now ``` +### Debian / Ubuntu Linux + +The Apple-specific steps (App Store update, iCloud, `mackup`) don't apply. +`./install` installs the apt prerequisites it needs (build tools, `zsh`, +`xclip`/`wl-clipboard`, …) and Linuxbrew, then symlinks everything. + +```bash +# 1. Update the system +sudo apt update && sudo apt upgrade + +# 2. Copy your SSH keys to ~/.ssh and make sure they're set to 600 +chmod 600 + +# 3. Clone this repo to ~/.dotfiles +cd ~; git clone git@github.com:marcaube/dotfiles.git .dotfiles + +# 4. Run the install script +cd ~/.dotfiles; ./install; cd - + +# 5. Log out and back in so the new login shell (zsh) takes effect +``` + ## Update @@ -61,7 +88,7 @@ git pull # 3. Reload the configs reload -# 4. Restore preferences +# 4. Restore preferences (macOS only) mackup restore ``` @@ -79,10 +106,14 @@ For example, that's where you could define your `GIT_AUTHOR_NAME` and ## Features +- cross-platform: works on macOS (Apple Silicon) and Debian/Ubuntu Linux — the + `./install` wrapper layers an OS-specific Dotbot config on top of a shared one, + and the shell config branches at runtime via `zsh/lib/os.zsh` - sensible [`macos`](./macos/macos.sh) configs, see [this repo](https://github.com/kevinSuttle/MacOS-Defaults) for more options -- a [`Brewfile`](./macos/Brewfile) with a list of binaries and apps I use all the time, see [the caskroom](https://caskroom.github.io/search) for more - - binaries are installed with [Homebrew](http://brew.sh/) - - apps are either installed with Homebrew Cask or [MAS](https://github.com/mas-cli/mas) +- split Brewfiles: [`brew/Brewfile.common`](./brew/Brewfile.common) (cross-platform CLI tools) and + [`brew/Brewfile.darwin`](./brew/Brewfile.darwin) (macOS casks + Mac App Store apps), see [the caskroom](https://caskroom.github.io/search) for more + - binaries are installed with [Homebrew](http://brew.sh/) (Linuxbrew on Linux) + - apps are either installed with Homebrew Cask or [MAS](https://github.com/mas-cli/mas) (macOS only) - an [`aliases.zsh`](./zsh/aliases.zsh) file with a bunch of useful aliases and functions - CLI app launchers - encoding and crypto utility functions @@ -101,10 +132,13 @@ For example, that's where you could define your `GIT_AUTHOR_NAME` and - [`macos.sh`](./macos/macos.sh) contains my macOS preferences - [`zshrc`](./zsh/zshrc) contains my Z shell config (plugins are sourced directly, no Oh My ZSH) - [`aliases.zsh`](./zsh/aliases.zsh) defines a list of useful CLI aliases, shortcuts and functions -- [`Brewfile`](./macos/Brewfile) contains the list of binaries (homebrew) and apps (cask and mas) that I want installed on my system +- [`brew/Brewfile.common`](./brew/Brewfile.common) and [`brew/Brewfile.darwin`](./brew/Brewfile.darwin) list the binaries (homebrew) and apps (cask and mas) I want installed - [`exports.zsh`](./zsh/exports.zsh) contains the `PATH` env variable configs +- [`zsh/lib/os.zsh`](./zsh/lib/os.zsh) detects the platform and exposes `is_macos`/`is_linux` so the rest of the shell config can branch +- `zsh/aliases.darwin.zsh` / `zsh/aliases.linux.zsh` hold the platform-specific aliases (sourced as `aliases.${OS}.zsh`) - `zsh/extra.zsh` is a file ignored by git where you can add your git credentials, custom commands, private aliases, etc. - [`install`](./install) is the installation script to make this all work "automagically" +- `install.conf.common.yaml` plus `install.conf.darwin.yaml` / `install.conf.linux.yaml` are the [Dotbot](https://github.com/anishathalye/dotbot) configs - [`init.lua`](./vim/init.lua) is my [Neovim](https://neovim.io/) entry point — modular config under `vim/lua/`, plugins managed by [lazy.nvim](https://github.com/folke/lazy.nvim) diff --git a/macos/Brewfile b/brew/Brewfile.common similarity index 53% rename from macos/Brewfile rename to brew/Brewfile.common index ac29def..415b34b 100644 --- a/macos/Brewfile +++ b/brew/Brewfile.common @@ -1,11 +1,10 @@ -# Taps -tap 'remind101/formulae' # for assume-role -tap 'yqrashawn/goku' # For Goku +# Cross-platform Homebrew packages — work on both macOS and Linuxbrew. +# Bundled on every platform. macOS-only formulae, casks and MAS apps live in +# Brewfile.darwin. -# Install Binaries +# CLI utilities brew 'atuin' # shell history on steroids brew 'bat' # like 'cat', with highlighting, git support and pagination -brew 'coreutils' # Those that come with macOS are outdated brew 'curl' brew 'direnv' # Load/unload environment variables based on $PWD brew 'docker-compose' # Docker without the hassle @@ -16,21 +15,17 @@ brew 'fd' # a nicer 'find' brew 'fzf' # Fuzzy search brew 'gawk' # Like awk, but with a g. Not to be confused with the large bird of prey brew 'git-delta' # Provides much better diffs highlighting in the terminal -brew 'gnu-sed' # sed, but a better version than what ships with OSX brew 'graphviz' # graph/diagrams generation brew 'htop' brew 'httpie' # to test HTTP calls from the CLI brew 'hyperfine' # a CLI benchmarking tool, to compare the performance of different commands brew 'jc' # To convert the output of commands to json, pairs nicely with jq brew 'jq' # When you want to manipulate JSON piped from httpie -brew 'mackup' # to sync my Mac configs -brew 'mas' # to install Mac App Store apps from the CLI brew 'organize-tool' # file management automation tool brew 'ouch' # easily compress/decompress files and directories brew 'ripgrep' # like grep, but faster brew 'rm-improved' # a better rm, with trash support brew 'starship' # a minimalist CLI prompt -brew 'telnet' brew 'tldr' # like 'man' but for lazy people with deadlines brew 'tmux' brew 'tmuxinator' @@ -52,17 +47,9 @@ brew 'lazygit' # terminal UI for git brew 'neovim' # the editor brew 'tree-sitter-cli' # CLI used by nvim-treesitter (main) to build parsers -# Productivity and automation (see: https://tighten.com/blog/how-to-train-your-keyboard/) -cask 'karabiner-elements' # Remap keys and shortcuts at the kernel level -cask 'hammerspoon' # Take Karabiner's shortcuts and make the magic happen -cask 'handy' # Offline speech-to-text for dictating text and commands -brew 'yqrashawn/goku/goku' # Manage Karabiner configs with ease - # Tools for work # brew 'assume-role' # brew 'awscli' -# cask 'aws-vpn-client' -# cask 'session-manager-plugin' # brew 'gettext' # to compile messages, don't forget to link! # brew 'libjpeg' # brew 'libmagic' @@ -86,55 +73,3 @@ brew 'gdb' # the basic Binjitsu weapon brew 'pngcheck' brew 'sqlmap' # SQL injection brew 'zbar' # barcode /qrcode reading -cask 'burp-suite' -cask 'hex-fiend' # a simple GUI hex editor - -# Apps -cask '1password' -cask 'alfred' -cask 'anki' # flash cards, for learning and memorization (active recall, spaced repetition, etc) -cask 'caffeine' # when I don't want my computer to fall asleep -cask 'cleanshot' # Gorgeous, expansive (and expensive!) screeshot app -cask 'dash' # offline docs and code snippets FTW! -cask 'deckset' # slide decks for conference talks -cask 'docker-desktop' -cask 'firefox' -cask 'hiddenbar' # keep my statusbar tidy -cask 'heynote' # a nice scratchpad app -cask 'imagealpha' # optimize transparent PNGs -cask 'imageoptim' # optimize images -cask 'insomnia' # a nice API client GUI -cask 'pycharm' -cask 'kaleidoscope@2' -cask 'keybase' -cask 'keycastr' # when pairing remotely, to show all keypresses in vim/neovim -cask 'kitty' -cask 'ngrok' -cask 'obsidian' -cask 'rectangle' # no better ways to move windows around -cask 'slack' -cask 'sublime-text' -cask 'zoom' - -# Gaming -cask 'battle-net' -cask 'discord' - -# Quicklook -cask 'betterzip' # preview zip archives -cask 'qlmarkdown' # preview Markdown files -cask 'qlprettypatch' # preview diff patches -cask 'quicklook-csv' # preview CSV files - -# Fonts -cask 'font-fira-code' -cask 'font-hack-nerd-font' # This font adds support for nice icons in NerdTree/NVIMTREE - -# Install Mac App Store apps -mas 'Bear', id: 1091189122 # note taking app -mas 'Ivory', id: 6444602274 # Mastodon/Fediverse client -mas 'Next Meeting', id: 1017470484 # See what meeting is up next, right in the status bar -mas 'Pixelmator Pro', id: 1289583905 -mas 'Reeder', id: 1529448980 # RSS reader -mas 'stts', id: 1187772509 # status of cloud services, right in the status bar -mas 'Vimari', id: 1480933944 # vim bindings for Safari diff --git a/brew/Brewfile.darwin b/brew/Brewfile.darwin new file mode 100644 index 0000000..cd94a2e --- /dev/null +++ b/brew/Brewfile.darwin @@ -0,0 +1,73 @@ +# macOS-only Homebrew packages: casks, Mac App Store apps and formulae that +# only make sense on macOS. Bundled after Brewfile.common on macOS only. + +# Taps +tap 'remind101/formulae' # for assume-role +tap 'yqrashawn/goku' # For Goku + +# Formulae that ship natively on Linux or are macOS-specific +brew 'coreutils' # Those that come with macOS are outdated (Linux has GNU natively) +brew 'gnu-sed' # sed, but a better version than what ships with OSX +brew 'mackup' # to sync my Mac configs +brew 'mas' # to install Mac App Store apps from the CLI +brew 'telnet' # Homebrew's telnet formula is macOS-only; Linux gets it via apt + +# Productivity and automation (see: https://tighten.com/blog/how-to-train-your-keyboard/) +cask 'karabiner-elements' # Remap keys and shortcuts at the kernel level +cask 'hammerspoon' # Take Karabiner's shortcuts and make the magic happen +cask 'handy' # Offline speech-to-text for dictating text and commands +brew 'yqrashawn/goku/goku' # Manage Karabiner configs with ease + +# Pentesting / CTF / Forensics (GUI) +cask 'burp-suite' +cask 'hex-fiend' # a simple GUI hex editor + +# Apps +cask '1password' +cask 'alfred' +cask 'anki' # flash cards, for learning and memorization (active recall, spaced repetition, etc) +cask 'caffeine' # when I don't want my computer to fall asleep +cask 'cleanshot' # Gorgeous, expansive (and expensive!) screeshot app +cask 'dash' # offline docs and code snippets FTW! +cask 'deckset' # slide decks for conference talks +cask 'docker-desktop' +cask 'firefox' +cask 'hiddenbar' # keep my statusbar tidy +cask 'heynote' # a nice scratchpad app +cask 'imagealpha' # optimize transparent PNGs +cask 'imageoptim' # optimize images +cask 'insomnia' # a nice API client GUI +cask 'pycharm' +cask 'kaleidoscope@2' +cask 'keybase' +cask 'keycastr' # when pairing remotely, to show all keypresses in vim/neovim +cask 'kitty' +cask 'ngrok' +cask 'obsidian' +cask 'rectangle' # no better ways to move windows around +cask 'slack' +cask 'sublime-text' +cask 'zoom' + +# Gaming +cask 'battle-net' +cask 'discord' + +# Quicklook +cask 'betterzip' # preview zip archives +cask 'qlmarkdown' # preview Markdown files +cask 'qlprettypatch' # preview diff patches +cask 'quicklook-csv' # preview CSV files + +# Fonts +cask 'font-fira-code' +cask 'font-hack-nerd-font' # This font adds support for nice icons in NerdTree/NVIMTREE + +# Install Mac App Store apps +mas 'Bear', id: 1091189122 # note taking app +mas 'Ivory', id: 6444602274 # Mastodon/Fediverse client +mas 'Next Meeting', id: 1017470484 # See what meeting is up next, right in the status bar +mas 'Pixelmator Pro', id: 1289583905 +mas 'Reeder', id: 1529448980 # RSS reader +mas 'stts', id: 1187772509 # status of cloud services, right in the status bar +mas 'Vimari', id: 1480933944 # vim bindings for Safari diff --git a/install b/install index cb0546c..ae2a08a 100755 --- a/install +++ b/install @@ -2,14 +2,29 @@ set -e -CONFIG="install.conf.yaml" DOTBOT_DIR="git/submodules/dotbot" DOTBOT_BIN="bin/dotbot" BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# Pick the OS-specific config to layer on top of the common one. +case "$(uname -s)" in + Darwin) OS_CONFIG="install.conf.darwin.yaml" ;; + Linux) OS_CONFIG="install.conf.linux.yaml" ;; + *) echo "Unsupported OS: $(uname -s)" >&2; exit 1 ;; +esac + cd "${BASEDIR}" git -C "${DOTBOT_DIR}" submodule sync --quiet --recursive git submodule update --init --recursive "${DOTBOT_DIR}" -"${BASEDIR}/${DOTBOT_DIR}/${DOTBOT_BIN}" -d "${BASEDIR}" --plugin-dir git/submodules/dotbot-gh-extension -c "${CONFIG}" "${@}" +# Dotbot's -c takes a single config file, so merge the common and OS-specific +# configs (both are top-level YAML sequences) into one before running. +MERGED_CONFIG="$(mktemp)" +trap 'rm -f "${MERGED_CONFIG}"' EXIT +cat "install.conf.common.yaml" "${OS_CONFIG}" > "${MERGED_CONFIG}" + +"${BASEDIR}/${DOTBOT_DIR}/${DOTBOT_BIN}" -d "${BASEDIR}" \ + --plugin-dir git/submodules/dotbot-gh-extension \ + -c "${MERGED_CONFIG}" \ + "${@}" diff --git a/install.conf.yaml b/install.conf.common.yaml similarity index 50% rename from install.conf.yaml rename to install.conf.common.yaml index 6346dbf..ceca400 100644 --- a/install.conf.yaml +++ b/install.conf.common.yaml @@ -1,3 +1,7 @@ +# Cross-platform Dotbot config. Run on every platform, before the +# OS-specific config (install.conf.darwin.yaml / install.conf.linux.yaml) +# selected by the ./install wrapper. + - defaults: link: relink: true @@ -8,8 +12,17 @@ - description: Validating sudo credentials command: "[ -n \"$CI\" ] || sudo --validate" + - description: Installing Debian/Ubuntu prerequisites + command: | + if [ "$(uname -s)" = "Linux" ] && command -v apt-get >/dev/null 2>&1; then + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends \ + zsh build-essential procps curl file git \ + xclip wl-clipboard libnotify-bin telnet + fi + - description: Installing XCode Command Line Tools - command: "[ -n \"$CI\" ] || [ -x \"$(which xcodebuild)\" ] || xcode-select --install" + command: "[ \"$(uname -s)\" != \"Darwin\" ] || [ -n \"$CI\" ] || [ -x \"$(which xcodebuild)\" ] || xcode-select --install" - clean: ['~'] @@ -18,9 +31,6 @@ - ~/.tmux/plugins - ~/bin - ~/.config/gh-dash - - ~/.config/karabiner - - ~/Library/Application Support/lazygit - - ~/Library/Application Support/organize - link: # Binaries @@ -57,26 +67,11 @@ # Kitty shell ~/.config/kitty: kitty - # Lazygit - ~/Library/Application Support/lazygit/config.yml: - path: lazygit/config.yml - force: true - # gh-dash ~/.config/gh-dash/config.yml: path: gh-dash/config.yml force: true - # Karabiner, Hammerspoon and Goku - ~/.config/karabiner.edn: karabiner/karabiner.edn - ~/.hammerspoon: hammerspoon - - # Mackup - ~/.mackup.cfg: macos/mackup.cfg - - # Organize - ~/Library/Application Support/organize/config.yaml: macos/organize.yaml - # Tmux ~/.tmux.conf: tmux/tmux.conf @@ -88,13 +83,30 @@ - shell: - description: Installing Homebrew - command: "[ -x \"$(which brew)\" ] || /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)\"" + command: "[ -x \"$(command -v brew)\" ] || /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)\"" - description: Updating Homebrew - command: "brew update" + command: | + for b in /opt/homebrew/bin/brew /home/linuxbrew/.linuxbrew/bin/brew /usr/local/bin/brew; do + [ -x "$b" ] && eval "$("$b" shellenv)" && break + done + brew update - description: Installing Brewfile bundle - command: "if [ -n \"$CI\" ]; then grep -E '^(tap|brew)' macos/Brewfile | brew bundle --file=-; else brew bundle --file=macos/Brewfile; fi" + command: | + for b in /opt/homebrew/bin/brew /home/linuxbrew/.linuxbrew/bin/brew /usr/local/bin/brew; do + [ -x "$b" ] && eval "$("$b" shellenv)" && break + done + brew bundle --file=brew/Brewfile.common || exit 1 + if [ "$(uname -s)" = "Darwin" ]; then + if [ -n "$CI" ]; then + # CI can't install casks or Mac App Store apps, so bundle only the + # taps and brew formulae from the macOS file. + grep -E '^(tap|brew)' brew/Brewfile.darwin | brew bundle --file=- || exit 1 + else + brew bundle --file=brew/Brewfile.darwin || exit 1 + fi + fi stdout: true stderr: true @@ -103,10 +115,10 @@ command: "[ -f ~/.hushlogin ] || touch ~/.hushlogin" - description: Adding ZSH to list of acceptable shells - command: "[ -n \"$CI\" ] || [[ \"$(ag $(which zsh) /etc/shells)\" ]] || sudo sh -c \"echo $(which zsh) >> /etc/shells\"" + command: "[ -n \"$CI\" ] || grep -q \"$(which zsh)\" /etc/shells || sudo sh -c \"echo $(which zsh) >> /etc/shells\"" - description: Making ZSH the default shell - command: "[ -n \"$CI\" ] || sudo chsh -s $(which zsh)" + command: "[ -n \"$CI\" ] || [ \"$SHELL\" = \"$(which zsh)\" ] || sudo chsh -s $(which zsh) $(whoami)" - description: Installing ZSH autocompletions command: "git clone https://github.com/zsh-users/zsh-completions ${DOTFILES}/zsh/plugins/zsh-completions 2> /dev/null || exit 0" @@ -124,12 +136,21 @@ command: "git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm || exit 0" - description: Clear sudo credential cache - command: "sudo --reset-timestamp" + command: "[ -n \"$CI\" ] || sudo --reset-timestamp" - description: Installing bat's theme - command: "mkdir -p \"$(bat --config-dir)/themes\" && wget -P \"$(bat --config-dir)/themes\" https://github.com/catppuccin/bat/raw/main/themes/Catppuccin%20Mocha.tmTheme && bat cache --build || exit 0" - -# Install `gh` extensions -- ghe: - - dlvhdr/gh-dash - - korosuke613/gh-user-stars + command: | + for b in /opt/homebrew/bin/brew /home/linuxbrew/.linuxbrew/bin/brew /usr/local/bin/brew; do + [ -x "$b" ] && eval "$("$b" shellenv)" && break + done + mkdir -p "$(bat --config-dir)/themes" && \ + wget -P "$(bat --config-dir)/themes" https://github.com/catppuccin/bat/raw/main/themes/Catppuccin%20Mocha.tmTheme && \ + bat cache --build || exit 0 + + - description: Installing gh extensions + command: | + for b in /opt/homebrew/bin/brew /home/linuxbrew/.linuxbrew/bin/brew /usr/local/bin/brew; do + [ -x "$b" ] && eval "$("$b" shellenv)" && break + done + gh extension install dlvhdr/gh-dash 2>/dev/null || true + gh extension install korosuke613/gh-user-stars 2>/dev/null || true diff --git a/install.conf.darwin.yaml b/install.conf.darwin.yaml new file mode 100644 index 0000000..4022871 --- /dev/null +++ b/install.conf.darwin.yaml @@ -0,0 +1,22 @@ +# macOS-only Dotbot config. Run after install.conf.common.yaml on macOS. + +- create: + - ~/.config/karabiner + - ~/Library/Application Support/lazygit + - ~/Library/Application Support/organize + +- link: + # Lazygit + ~/Library/Application Support/lazygit/config.yml: + path: lazygit/config.yml + force: true + + # Karabiner, Hammerspoon and Goku + ~/.config/karabiner.edn: karabiner/karabiner.edn + ~/.hammerspoon: hammerspoon + + # Mackup + ~/.mackup.cfg: macos/mackup.cfg + + # Organize + ~/Library/Application Support/organize/config.yaml: macos/organize.yaml diff --git a/install.conf.linux.yaml b/install.conf.linux.yaml new file mode 100644 index 0000000..dc4f63f --- /dev/null +++ b/install.conf.linux.yaml @@ -0,0 +1,15 @@ +# Linux-only Dotbot config. Run after install.conf.common.yaml on Linux. +# Tools that store config under ~/Library on macOS use ~/.config here. + +- create: + - ~/.config/lazygit + - ~/.config/organize + +- link: + # Lazygit + ~/.config/lazygit/config.yml: + path: lazygit/config.yml + force: true + + # Organize + ~/.config/organize/config.yaml: macos/organize.yaml diff --git a/tests/os.bats b/tests/os.bats new file mode 100644 index 0000000..5f90b39 --- /dev/null +++ b/tests/os.bats @@ -0,0 +1,52 @@ +#!/usr/bin/env bats + +# Cross-platform / OS-conditional behaviour. + +@test "os.zsh detects the running platform" { + run zsh -c "source $DOTFILES/zsh/lib/os.zsh && echo \$OS" + [ "$status" -eq 0 ] + case "$(uname -s)" in + Darwin) [ "$output" = "darwin" ] ;; + Linux) [ "$output" = "linux" ] ;; + esac +} + +@test "is_macos / is_linux agree with uname" { + if [ "$(uname -s)" = "Darwin" ]; then + run zsh -c "source $DOTFILES/zsh/lib/os.zsh && is_macos && ! is_linux" + else + run zsh -c "source $DOTFILES/zsh/lib/os.zsh && is_linux && ! is_macos" + fi + [ "$status" -eq 0 ] +} + +@test "OS-specific aliases file sources without errors" { + os_file="$DOTFILES/zsh/aliases.$(zsh -c "source $DOTFILES/zsh/lib/os.zsh && echo \$OS").zsh" + [ -f "$os_file" ] || skip "no OS-specific aliases file for this platform" + run zsh -c "source $DOTFILES/zsh/lib/os.zsh && source $os_file" + [ "$status" -eq 0 ] +} + +@test "lazygit config is symlinked to the right place for this OS" { + if [ "$(uname -s)" = "Darwin" ]; then + [ -L "$HOME/Library/Application Support/lazygit/config.yml" ] + else + [ -L "$HOME/.config/lazygit/config.yml" ] + fi +} + +@test "Homebrew is on PATH after an interactive shell loads" { + run zsh -i -c 'command -v brew' + [ "$status" -eq 0 ] +} + +@test "clipboard helper is available (Linux uses a shim)" { + if [ "$(uname -s)" = "Darwin" ]; then + command -v pbcopy + else + # On Linux pbcopy is defined as a function in aliases.linux.zsh when a + # backend (wl-copy / xclip) is present. + run zsh -i -c 'whence -w pbcopy || command -v wl-copy || command -v xclip' + [ "$status" -eq 0 ] + fi +} diff --git a/tests/zsh.bats b/tests/zsh.bats index d2e854f..8a5657c 100644 --- a/tests/zsh.bats +++ b/tests/zsh.bats @@ -13,7 +13,7 @@ } @test "exports.zsh sources without errors" { - run zsh -c "source $DOTFILES/zsh/exports.zsh" + run zsh -c "source $DOTFILES/zsh/lib/os.zsh && source $DOTFILES/zsh/exports.zsh" [ "$status" -eq 0 ] } @@ -23,13 +23,14 @@ } @test "EDITOR is set to nvim" { - run zsh -c "source $DOTFILES/zsh/exports.zsh && echo \$EDITOR" + run zsh -c "source $DOTFILES/zsh/lib/os.zsh && source $DOTFILES/zsh/exports.zsh && echo \$EDITOR" [ "$status" -eq 0 ] [ "$output" = "nvim" ] } -@test "PATH includes Homebrew coreutils" { - run zsh -c "source $DOTFILES/zsh/exports.zsh && echo \$PATH" +@test "PATH includes Homebrew coreutils (macOS only)" { + [ "$(uname -s)" = "Darwin" ] || skip "coreutils gnubin shim is macOS-only" + run zsh -c "source $DOTFILES/zsh/lib/os.zsh && source $DOTFILES/zsh/exports.zsh && echo \$PATH" [ "$status" -eq 0 ] [[ "$output" == *"opt/coreutils"* ]] } diff --git a/zsh/aliases.darwin.zsh b/zsh/aliases.darwin.zsh new file mode 100644 index 0000000..a1df225 --- /dev/null +++ b/zsh/aliases.darwin.zsh @@ -0,0 +1,47 @@ +# macOS-only aliases and functions. +# Sourced by zshrc as aliases.${OS}.zsh when $OS == macos. +# pbcopy/pbpaste and `open` are macOS built-ins, so the portable aliases in +# aliases.zsh that rely on them work here without a shim. + +# Navigation into macOS-specific locations +alias icloud='cd ~/Library/Mobile\ Documents/com~apple~CloudDocs/;pwd' +alias library="cd $HOME/Library" + +# Launch Google Chrome +alias chromeopen='/usr/bin/open -a "/Applications/Google Chrome.app"' + +# Show/hide hidden files in Finder +alias hide='defaults write com.apple.finder AppleShowAllFiles -bool false && killall Finder' +alias show='defaults write com.apple.finder AppleShowAllFiles -bool true && killall Finder' + +# Show/hide all desktop icons (useful when presenting) +alias hidedesktop='defaults write com.apple.finder CreateDesktop -bool false && killall Finder' +alias showdesktop='defaults write com.apple.finder CreateDesktop -bool true && killall Finder' + +# Disable/enable Spotlight +alias spotoff="sudo mdutil -a -i off" +alias spoton="sudo mdutil -a -i on" + +# Display a notification (useful when executing time-consuming commands) +alias notify='/usr/bin/osascript -e "display notification \"Finished!\" with title \"Zsh\""' + +# Get macOS Software Updates, update Homebrew and its installed packages +alias update='sudo softwareupdate -i -a; brew update; brew upgrade; brew upgrade --cask; brew cleanup; mas upgrade;tldr --update' + +# Check if I've got outdated versions of applications +alias outdated='brew outdated; mas outdated' + +# IP addresses +alias localip='ipconfig getifaddr en0' +alias ips='ifconfig -a | grep -o '\''inet6\? \(addr:\)\?\s\?\(\(\([0-9]\+\.\)\{3\}[0-9]\+\)\|[a-fA-F0-9:]\+\)'\'' | awk '\''{ sub(/inet6? (addr:)? ?/, ""); print }'\''' + +# Show active network interfaces +alias ifactive="ifconfig | pcregrep -M -o '^[^\t:]+:([^\n]|\n\t)*status: active'" + +# Flush Directory Service (DNS) cache +alias flush='dscacheutil -flushcache && sudo killall -HUP mDNSResponder' + +# Shortcut for the macOS quick-look command +function ql() { + quick-look "$1" +} diff --git a/zsh/aliases.linux.zsh b/zsh/aliases.linux.zsh new file mode 100644 index 0000000..50a97dc --- /dev/null +++ b/zsh/aliases.linux.zsh @@ -0,0 +1,40 @@ +# Linux-only aliases and functions (Debian/Ubuntu assumed for package commands). +# Sourced by zshrc as aliases.${OS}.zsh when $OS == linux. +# +# Provides pbcopy/pbpaste and `open` shims so the portable aliases in +# aliases.zsh (pubkey, shrug, tableflip, md5, server) work unchanged. + +# Clipboard — prefer Wayland's wl-clipboard, fall back to X11's xclip. +if [[ -n "$WAYLAND_DISPLAY" ]] && command -v wl-copy >/dev/null 2>&1; then + pbcopy() { wl-copy } + pbpaste() { wl-paste } +elif command -v xclip >/dev/null 2>&1; then + pbcopy() { xclip -selection clipboard } + pbpaste() { xclip -selection clipboard -o } +fi + +# Open files/URLs with the desktop's default handler +if command -v xdg-open >/dev/null 2>&1; then + alias open='xdg-open' +fi + +# Desktop notification (useful when executing time-consuming commands) +if command -v notify-send >/dev/null 2>&1; then + alias notify='notify-send "Zsh" "Finished!"' +fi + +# Update the system and Homebrew packages +alias update='sudo apt update && sudo apt upgrade -y; command -v brew >/dev/null 2>&1 && { brew update; brew upgrade; brew cleanup; }; tldr --update' + +# Check for outdated Homebrew packages +alias outdated='command -v brew >/dev/null 2>&1 && brew outdated' + +# IP addresses +alias localip="hostname -I | awk '{print \$1}'" +alias ips="ip -o addr show | awk '{print \$2, \$4}'" + +# Flush systemd-resolved's DNS cache (no-op if not used) +alias flush='sudo resolvectl flush-caches 2>/dev/null || sudo systemd-resolve --flush-caches 2>/dev/null' + +# Quick-look equivalent: open in the default viewer +alias ql='xdg-open' diff --git a/zsh/aliases.zsh b/zsh/aliases.zsh index 16d1015..3f4f369 100644 --- a/zsh/aliases.zsh +++ b/zsh/aliases.zsh @@ -30,14 +30,11 @@ alias ...="cd ../.." alias code="cd ~/Code;pwd" alias dl='cd ~/Downloads' alias dotfiles="cd $DOTFILES" -alias icloud='cd ~/Library/Mobile\ Documents/com~apple~CloudDocs/;pwd' -alias library="cd $HOME/Library" alias www='cd ~/Code;pwd' # Application launchers alias cat='bat --theme="Catppuccin Mocha"' alias woman=tldr # Grace Hopper approved -alias chromeopen='/usr/bin/open -a "/Applications/Google Chrome.app"' # Some aliases I used from oh-my-zsh # see: https://github.com/ohmyzsh/ohmyzsh/blob/master/plugins/common-aliases/common-aliases.plugin.zsh @@ -51,20 +48,6 @@ alias mv='mv -i' alias cp='cp -i' alias rm='rm -i' -# Show/hide hidden files in Finder -alias hide='defaults write com.apple.finder AppleShowAllFiles -bool false && killall Finder' -alias show='defaults write com.apple.finder AppleShowAllFiles -bool true && killall Finder' - -# Show/hide all desktop icons (useful when presenting) -alias hidedesktop='defaults write com.apple.finder CreateDesktop -bool false && killall Finder' -alias showdesktop='defaults write com.apple.finder CreateDesktop -bool true && killall Finder' - -# Disable Spotlight -alias spotoff="sudo mdutil -a -i off" - -# Enable Spotlight -alias spoton="sudo mdutil -a -i on" - # Kill all the tabs in Chrome to free up memory # [C] explained: http://www.commandlinefu.com/commands/view/402/exclude-grep-from-your-grepped-output-of-ps-alias-included-in-description alias chromekill='ps ux | grep '\''[C]hrome Helper --type=renderer'\'' | grep -v extension-process | tr -s '\'' '\'' | cut -d '\'' '\'' -f2 | xargs kill' @@ -81,32 +64,14 @@ alias kp='ps -a | fzf --prompt="Choose process to kill: " | cut -d " " -f 1 | xa # For those times when you just don't look busy enough alias busy='export GREP_COLOR='\''1;32'\''; cat /dev/urandom | hexdump -C | grep --color=auto "ca fe"' -# Display a notification -# (useful when executing time-consuming commands) -alias notify='/usr/bin/osascript -e "display notification \"Finished!\" with title \"Zsh\""' - # Get week number alias week='date +%V' # Print each PATH entry on a separate line alias path='echo -e ${PATH//:/\\n}' -# Get macOS Software Updates, update Homebrew and its installed packages -alias update='sudo softwareupdate -i -a; brew update; brew upgrade; brew upgrade --cask; brew cleanup; mas upgrade;tldr --update' - -# Check if I've got outdated versions of applications -alias outdated='brew outdated; mas outdated' - -# IP addresses +# My public IP (works everywhere `dig` is available) alias ip="dig +short myip.opendns.com @resolver1.opendns.com" -alias localip='ipconfig getifaddr en0' -alias ips='ifconfig -a | grep -o '\''inet6\? \(addr:\)\?\s\?\(\(\([0-9]\+\.\)\{3\}[0-9]\+\)\|[a-fA-F0-9:]\+\)'\'' | awk '\''{ sub(/inet6? (addr:)? ?/, ""); print }'\''' - -# Show active network interfaces -alias ifactive="ifconfig | pcregrep -M -o '^[^\t:]+:([^\n]|\n\t)*status: active'" - -# Flush Directory Service (DNS) cache -alias flush='dscacheutil -flushcache && sudo killall -HUP mDNSResponder' # Pipe my public key to my clipboard. alias pubkey="more ~/.ssh/id_rsa.pub | pbcopy | echo '=> Public key copied to pasteboard.'" @@ -157,11 +122,6 @@ function scrapeUrl() { wget --adjust-extension --convert-links --page-requisites --span-hosts --no-host-directories "$1" } -# Shortcut for the OSX quick-look command -function ql() { - quick-look "$1" -} - # Function to easily match text using regexes in a command pipeline, second param is the capture group number # e.g. `find . -name '*.py' | xargs cat | regex '^class (\w+)' 1 | sort | uniq` to find all the classes in a Py project function regex() { diff --git a/zsh/exports.zsh b/zsh/exports.zsh index a4253c1..e5829ca 100644 --- a/zsh/exports.zsh +++ b/zsh/exports.zsh @@ -1,8 +1,19 @@ -# Make sure coreutils are loaded before system commands -export PATH="/opt/homebrew/opt/coreutils/libexec/gnubin:$PATH" +# Resolve Homebrew's prefix per-platform (/opt/homebrew on Apple Silicon, +# /home/linuxbrew/.linuxbrew on Linux) and put its tools on the PATH. +if [ -x /opt/homebrew/bin/brew ]; then + eval "$(/opt/homebrew/bin/brew shellenv)" +elif [ -x /home/linuxbrew/.linuxbrew/bin/brew ]; then + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" +fi + +# Make sure coreutils are loaded before system commands (macOS only — Linux +# ships GNU coreutils natively, so no gnubin shim is needed). +if is_macos && [ -d "$(brew --prefix)/opt/coreutils/libexec/gnubin" ]; then + export PATH="$(brew --prefix)/opt/coreutils/libexec/gnubin:$PATH" +fi # Local bin directories before anything else -export PATH="/$DOTFILES/bin:usr/local/bin:/usr/local/sbin:$PATH" +export PATH="$DOTFILES/bin:/usr/local/bin:/usr/local/sbin:$PATH" # Specify default editor. Possible values: vim, nano, ed etc. export EDITOR=nvim @@ -21,9 +32,12 @@ export HOMEBREW_NO_INSECURE_REDIRECT=1 # Abort installation of a cask if no checksum is defined export HOMEBREW_CASK_OPTS=--require-sha -# For psycopg2, see: https://github.com/psycopg/psycopg2/issues/890 -export LDFLAGS="-L/opt/homebrew/opt/openssl@3/lib" -export CPPFLAGS="-I/opt/homebrew/opt/openssl@3/include" +# macOS-only build flags for Homebrew-provided OpenSSL/LLVM. +if is_macos; then + # For psycopg2, see: https://github.com/psycopg/psycopg2/issues/890 + export LDFLAGS="-L$(brew --prefix)/opt/openssl@3/lib" + export CPPFLAGS="-I$(brew --prefix)/opt/openssl@3/include" -# For OpenSSL and LLVM -export PATH="/opt/homebrew/opt/llvm/bin:$PATH" + # For OpenSSL and LLVM + export PATH="$(brew --prefix)/opt/llvm/bin:$PATH" +fi diff --git a/zsh/lib/os.zsh b/zsh/lib/os.zsh new file mode 100644 index 0000000..2ccf8ae --- /dev/null +++ b/zsh/lib/os.zsh @@ -0,0 +1,12 @@ +# OS detection — sourced first so everything downstream can branch on platform. +# $OS mirrors the lowercased `uname -s`, matching the install.conf..yaml and +# aliases..zsh filenames. +case "$(uname -s)" in + Darwin) OS=darwin ;; + Linux) OS=linux ;; + *) OS=unknown ;; +esac +export OS + +is_macos() { [[ "$OS" == darwin ]]; } +is_linux() { [[ "$OS" == linux ]]; } diff --git a/zsh/zshrc b/zsh/zshrc index 52e637c..d8f7ef7 100644 --- a/zsh/zshrc +++ b/zsh/zshrc @@ -1,8 +1,14 @@ # Path to your dotfiles installation. export DOTFILES=$HOME/.dotfiles +# OS detection — must come first so everything below can branch on platform +source $DOTFILES/zsh/lib/os.zsh + # Aliases, exports and functions source $DOTFILES/zsh/aliases.zsh +if [ -f $DOTFILES/zsh/aliases.${OS}.zsh ]; then + source $DOTFILES/zsh/aliases.${OS}.zsh +fi source $DOTFILES/zsh/exports.zsh if [ -f $DOTFILES/zsh/extra.zsh ]; then source $DOTFILES/zsh/extra.zsh @@ -14,9 +20,10 @@ source $DOTFILES/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh source $DOTFILES/zsh/plugins/zsh-completions/zsh-completions.plugin.zsh source $DOTFILES/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh -# fzf — direct paths instead of the detection plugin -source /opt/homebrew/opt/fzf/shell/completion.zsh -source /opt/homebrew/opt/fzf/shell/key-bindings.zsh +# fzf — direct paths instead of the detection plugin (Homebrew prefix differs +# between macOS and Linux, so resolve it dynamically) +source "$(brew --prefix)/opt/fzf/shell/completion.zsh" +source "$(brew --prefix)/opt/fzf/shell/key-bindings.zsh" # Keep sensitive info out of history setopt HIST_IGNORE_SPACE