diff --git a/.aliases b/.aliases index fc13f7e..7e56426 100644 --- a/.aliases +++ b/.aliases @@ -302,8 +302,19 @@ if [[ $OS == 'Darwin' ]]; then # Stopwatch alias timer='echo "Timer started. Stop with Ctrl-D." && date && time cat && date' - # Get OS X Software Updates, and update installed Ruby gems, Homebrew, npm, and their installed packages - alias update='sudo softwareupdate -i -a; brew update; brew upgrade; brew cleanup; npm install npm -g; npm update -g; sudo gem update --system; sudo gem update' + # Get OS X Software Updates and apply nix-darwin / home-manager changes + update() { + sudo softwareupdate -i -a + if command -v darwin-rebuild &>/dev/null; then + nix flake update --flake ~/.dotfiles + darwin-rebuild switch --flake ~/.dotfiles + elif command -v nix &>/dev/null; then + nix flake update --flake ~/.dotfiles + home-manager switch --flake ~/.dotfiles + fi + npm install npm -g && npm update -g + sudo gem update --system && sudo gem update + } # IP addresses alias ip="dig +short myip.opendns.com @resolver1.opendns.com" diff --git a/.bashrc b/.bashrc index df13140..d11515c 100644 --- a/.bashrc +++ b/.bashrc @@ -46,11 +46,23 @@ if [ -r /etc/profile.d/bash_completion.sh ]; then source /etc/profile.d/bash_completion.sh fi -# brew +# nix & home-manager +if [ -e "${HOME}/.nix-profile/etc/profile.d/hm-session-vars.sh" ]; then + # shellcheck disable=SC1091 + source "${HOME}/.nix-profile/etc/profile.d/hm-session-vars.sh" +fi + +# brew (managed by nix-darwin on macOS) if command -v brew &>/dev/null; then eval "$(brew shellenv)" fi +# bash completions — Nix profile +if [ -r "${HOME}/.nix-profile/etc/profile.d/bash_completion.sh" ]; then + # shellcheck disable=SC1091 + source "${HOME}/.nix-profile/etc/profile.d/bash_completion.sh" +fi + # https://docs.brew.sh/Shell-Completion if [ -r "${HOMEBREW_PREFIX}/etc/profile.d/bash_completion.sh" ]; then export BASH_COMPLETION_COMPAT_DIR="${HOMEBREW_PREFIX}/etc/bash_completion.d" @@ -67,14 +79,21 @@ fi PROMPT_COMMAND="history -a;$PROMPT_COMMAND" # chruby -if [ -f "${HOMEBREW_PREFIX}/share/chruby/chruby.sh" ]; then +CHRUBY_DIR="" +if [ -f "${HOME}/.nix-profile/share/chruby/chruby.sh" ]; then + CHRUBY_DIR="${HOME}/.nix-profile/share/chruby" +elif [ -f "${HOMEBREW_PREFIX}/share/chruby/chruby.sh" ]; then + CHRUBY_DIR="${HOMEBREW_PREFIX}/share/chruby" +fi + +if [ -n "$CHRUBY_DIR" ]; then # shellcheck disable=SC2034 RUBIES=("${HOME}/.rubies/*") # shellcheck source=/dev/null - source "${HOMEBREW_PREFIX}/share/chruby/chruby.sh" + source "${CHRUBY_DIR}/chruby.sh" # shellcheck source=/dev/null - source "${HOMEBREW_PREFIX}/share/chruby/auto.sh" + source "${CHRUBY_DIR}/auto.sh" fi if command -v zoxide &>/dev/null; then @@ -86,7 +105,7 @@ if command -v direnv &>/dev/null; then fi # gcloud -# For Homebrew-installed gcloud-cli +# gcloud-cli is managed by nix-darwin's Homebrew module on macOS if command -v brew &>/dev/null; then GCLOUD_SDK="${HOMEBREW_PREFIX}/share/google-cloud-sdk" if [ -f "${GCLOUD_SDK}/path.bash.inc" ] && diff --git a/.brew b/.brew deleted file mode 100755 index 2ad483e..0000000 --- a/.brew +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o pipefail -set -o nounset - -DEBUG=${DEBUG:=0} -[[ $DEBUG -eq 1 ]] && set -o xtrace - -export HOMEBREW_CASK_OPTS="--appdir=${HOME}/Applications --fontdir=${HOME}/Library/Fonts --no-binaries --require-sha" -export HOMEBREW_NO_ANALYTICS=1 -export HOMEBREW_NO_AUTO_UPDATE=1 -export HOMEBREW_NO_INSECURE_REDIRECT=1 -export PATH="${HOME}/.homebrew/bin:${PATH}" - -CPU_BRAND="$(sysctl -n machdep.cpu.brand_string)" -export CPU_BRAND - -echo "--> Detected CPU Brand: ${CPU_BRAND}" - -echo "--> Make sure we're using the latest Homebrew" -brew update - -echo '--> Upgrade any already-installed formulae' -brew upgrade - -echo '--> Install all brew Packages' -brew install \ - ack \ - advancecomp \ - anki \ - ansifilter \ - ast-grep \ - autoenv \ - awscli \ - bash \ - bash-completion@2 \ - bat \ - bazel \ - binutils \ - cheat \ - chruby \ - cli53 \ - closure-compiler \ - coreutils \ - ctags \ - curl \ - diff-so-fancy \ - diffutils \ - ed \ - editorconfig \ - entr \ - eza \ - fd \ - findomain \ - findutils \ - firefox \ - font-hack \ - font-hack-nerd-font \ - font-jetbrains-mono \ - fzf \ - gawk \ - gcc \ - gcloud-cli \ - ghostty \ - gifsicle \ - git \ - gitlint \ - gnu-indent \ - gnu-sed \ - gnu-tar \ - gnu-which \ - go \ - go-jsonnet \ - google-chrome \ - gpatch \ - gpg \ - grep \ - gsl \ - gzip \ - hadolint \ - htmlcompressor \ - htop \ - imageoptim \ - ipcalc \ - iterm2 \ - jhead \ - jpeg \ - jpegoptim \ - jq \ - k9s \ - kops \ - kubectx \ - lazygit \ - less \ - lesspipe \ - libyaml \ - logcli \ - luarocks \ - make \ - miller \ - micro-snitch \ - moreutils \ - nmap \ - omnigraffle \ - openssh \ - openssl \ - optipng \ - packer \ - pigz \ - pngcrush \ - pngquant \ - pre-commit \ - prettier \ - pv \ - pwgen \ - python@3.9 \ - readline \ - ripgrep \ - ruby-build \ - ruby-install \ - ruff \ - s3cmd \ - shellcheck \ - shfmt \ - shpotify \ - spotify \ - ssh-copy-id \ - starship \ - stow \ - tcpdump \ - terminal-notifier \ - tfenv \ - the_silver_searcher \ - tig \ - tldr \ - tmux \ - tor-browser \ - tree \ - vale \ - vim \ - vlc \ - watch \ - wget \ - xz \ - zoxide - -# 1password and little-snitch should be installed globally ... -brew install --appdir /Applications \ - 1password \ - little-snitch - -# Yes, I use a non privileged user for daily use -echo "--> login as admin to install some casks system-wide in /Applications" - -su admin -c " -while true; do sudo -n true; sleep 60; kill -0 $$ || exit; done 2>/dev/null & - -make -f ${HOME}/.dotfiles/Makefile install-homebrew - -export HOMEBREW_CASK_OPTS='--appdir=/Applications --no-binaries --require-sha' -export PATH='/Users/admin/.homebrew/bin:${PATH}' - -echo '--> Install/update Homebrew cask' - -brew update - -brew tap homebrew/cask-drivers - -brew install \ - gpg-suite \ - keybase \ - vagrant \ - virtualbox -" - -## ruby -mkdir -p "${HOME}/.rubies" - -RUBIES=( - 3.4.1 - 3.3.6 - 3.2.6 -) - -for ruby in "${RUBIES[@]}"; do - if [[ ! -f "${HOME}/.rubies/ruby-${ruby}/bin/ruby" ]]; then - "${HOME}/.homebrew/bin/ruby-install" "ruby" "$ruby" - fi -done - -# Disable the macOS default ssh-agent for your user. -launchctl disable user/$UID/com.openssh.ssh-agent diff --git a/.gitignore b/.gitignore index 5278fd7..4aca293 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ .config .gnupg/ nvim.appimage +result +.direnv diff --git a/.profile b/.profile index 88d42cd..0afedc7 100644 --- a/.profile +++ b/.profile @@ -1,7 +1,16 @@ +# Nix +if [ -e "${HOME}/.nix-profile/etc/profile.d/nix.sh" ]; then + # shellcheck disable=SC1091 + . "${HOME}/.nix-profile/etc/profile.d/nix.sh" +elif [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then + # shellcheck disable=SC1091 + . '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' +fi + # PATH -export PATH="$HOME/.homebrew/bin:$HOME/.homebrew/sbin:$HOME/bin:/usr/local/bin:/usr/local/sbin:$PATH" +export PATH="$HOME/.nix-profile/bin:$HOME/.homebrew/bin:$HOME/.homebrew/sbin:$HOME/bin:/usr/local/bin:/usr/local/sbin:$PATH" -# Homebrew prefix +# Homebrew prefix (managed by nix-darwin on macOS) export HOMEBREW_PREFIX='/usr/local' if command -v brew >/dev/null 2>&1; then HOMEBREW_PREFIX="$(brew --prefix)" @@ -54,7 +63,7 @@ elif less_pipe=$(command -v lesspipe.sh); then fi # man -export MANPATH="$HOME/.homebrew/share/man${MANPATH+:$MANPATH}:" +export MANPATH="$HOME/.nix-profile/share/man:$HOME/.homebrew/share/man${MANPATH+:$MANPATH}:" # Don't clear the screen after quitting a manual page export MANPAGER='less -X' @@ -101,7 +110,9 @@ if [ ! -d "$TMPDIR" ]; then fi # diff-highligh -if [ -d "${HOMEBREW_PREFIX}/share/git-core/contrib/diff-highlight/" ]; then +if [ -d "${HOME}/.nix-profile/share/git-core/contrib/diff-highlight/" ]; then + export PATH="${PATH}:${HOME}/.nix-profile/share/git-core/contrib/diff-highlight" +elif [ -d "${HOMEBREW_PREFIX}/share/git-core/contrib/diff-highlight/" ]; then export PATH="${PATH}:${HOMEBREW_PREFIX}/share/git-core/contrib/diff-highlight" fi diff --git a/Makefile b/Makefile index 3e948e7..c026383 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,19 @@ -OS = "$(uname)" +OS := $(shell uname) SHELL := /usr/bin/env bash +SYSTEM := $(shell uname -m)-$(shell uname -s | tr '[:upper:]' '[:lower:]') + +# Map uname output to Nix system identifiers +ifeq ($(SYSTEM),arm64-darwin) + NIX_SYSTEM := aarch64-darwin +else ifeq ($(SYSTEM),x86_64-darwin) + NIX_SYSTEM := x86_64-darwin +else ifeq ($(SYSTEM),x86_64-linux) + NIX_SYSTEM := x86_64-linux +else ifeq ($(SYSTEM),aarch64-linux) + NIX_SYSTEM := aarch64-linux +else + NIX_SYSTEM := x86_64-linux +endif .DEFAULT_GOAL := help @@ -11,9 +25,12 @@ help: install: ## Install all the things @make install-dotfiles \ install-vundle \ - install-nvim-deps \ - @[[ $OS == 'Darwin' ]] \ - && make install-homebrew run-brew + install-nvim-deps + @if [[ "$(OS)" == 'Darwin' ]]; then \ + make run-darwin; \ + else \ + make run-nix; \ + fi install-dotfiles: ## Pull and Install dotfiles @git pull -q && git submodule update --init --recursive -q @@ -26,18 +43,14 @@ install-dotfiles: ## Pull and Install dotfiles --ignore='README.md' \ --ignore='LICENCE' \ --ignore='Makefile' \ + --ignore='flake.nix' \ + --ignore='flake.lock' \ + --ignore='home.nix' \ + --ignore='darwin.nix' \ --override='.bashrc' \ --override='.profile' \ --override='.bash_profile' -install-homebrew: ## Install homebrew, the missing package manager for OS X - $(info --> Install homebrew) - @mkdir -m 0700 -p ~/.homebrew - @if [[ ! -f ~/.homebrew/bin/brew ]]; then \ - curl -L https://github.com/Homebrew/brew/tarball/master \ - | tar xz --strip 1 -C ~/.homebrew; \ - fi - install-vundle: ## Install Vundle, the plug-in manager for Vim $(info --> Install Vundle) @mkdir -p ~/.vim/bundle/ ~/.vimswap ~/.vimundo ~/.tmp @@ -49,12 +62,37 @@ install-nvim-deps: ## Install/setup Neovim dependencies $(info --> Setup Neovim directories) @mkdir -p ~/.vimswap ~/.vimundo ~/.tmp +install-rubies: ## Install Ruby versions via ruby-install + $(info --> Install Ruby versions) + @mkdir -p ~/.rubies + @for ruby in 3.4.1 3.3.6 3.2.6; do \ + if [[ ! -f "$$HOME/.rubies/ruby-$$ruby/bin/ruby" ]]; then \ + ruby-install ruby "$$ruby"; \ + fi; \ + done + pre-commit: ## Run pre-commit tests $(info --> Run pre-commit) @pre-commit run --all-files -run-brew: ## Run ./.brew - @bash -x .brew +run-darwin: ## Apply nix-darwin configuration (macOS — includes home-manager + Homebrew casks) + $(info --> Apply nix-darwin configuration for $(NIX_SYSTEM)) + @command -v darwin-rebuild >/dev/null && { \ + darwin-rebuild switch --flake '.#$(NIX_SYSTEM)'; \ + } || { \ + echo 'Bootstrapping nix-darwin for the first time...'; \ + nix run nix-darwin -- switch --flake '.#$(NIX_SYSTEM)'; \ + } + +run-nix: ## Apply Nix home-manager configuration (Linux) + $(info --> Apply home-manager configuration for $(NIX_SYSTEM)) + @command -v home-manager >/dev/null || { \ + echo 'home-manager not found. Install Nix and home-manager first:'; \ + echo ' curl --proto "=https" --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install'; \ + echo ' nix run home-manager -- switch --flake .#$(NIX_SYSTEM)'; \ + exit 1; \ + } + @home-manager switch --flake '.#$(NIX_SYSTEM)' test: ## Run tests suite @$(MAKE) pre-commit @@ -66,4 +104,8 @@ uninstall-dotfiles: ## Uninstall dotfiles --ignore='.github' \ --ignore='README.md' \ --ignore='LICENCE' \ - --ignore='Makefile' + --ignore='Makefile' \ + --ignore='flake.nix' \ + --ignore='flake.lock' \ + --ignore='home.nix' \ + --ignore='darwin.nix' diff --git a/README.md b/README.md index ef0cb4f..afa6682 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,14 @@ Personal dotfiles for macOS, managed with [GNU Stow](https://www.gnu.org/software/stow/). +On macOS, system configuration and packages are managed declaratively with +[nix-darwin](https://github.com/LnL7/nix-darwin) (which includes +[home-manager](https://nix-community.github.io/home-manager/) for user-level +packages and [Homebrew](https://brew.sh/) for GUI / cask applications). + +On Linux, [home-manager](https://nix-community.github.io/home-manager/) is +used standalone for CLI tools. + ## What's included | File / directory | Description | @@ -13,7 +21,9 @@ Personal dotfiles for macOS, managed with | `.config/ghostty/` | [Ghostty](https://ghostty.org) terminal with Dracula colour scheme | | `.aliases` | Handy shell aliases | | `.bash_profile` / `.bashrc` / `.profile` | Bash startup files | -| `.brew` | Homebrew formulae and casks | +| `flake.nix` | Nix flake (nix-darwin + home-manager) | +| `darwin.nix` | macOS system config (Homebrew casks, launchd, …) | +| `home.nix` | Home-manager config (CLI tools, shared across OS) | | `.gitconfig` | Git settings (diff-so-fancy, GPG signing, …) | | `.gitmessage` | Conventional Commits message template | | `.gnupg/` | GPG configuration | @@ -29,6 +39,22 @@ Personal dotfiles for macOS, managed with - **git** - **[GNU Stow](https://www.gnu.org/software/stow/)** — `brew install stow` (macOS) or `apt install stow` (Debian/Ubuntu) +- **[Nix](https://nixos.org/)** — install with the + [Determinate Nix Installer](https://zero-to-nix.com/start/install): + + ```bash + curl --proto '=https' --tlsv1.2 -sSf -L \ + https://install.determinate.systems/nix | sh -s -- install + ``` + +- **[Homebrew](https://brew.sh/)** (macOS only) — nix-darwin manages casks + through Homebrew, so it must be installed first: + + ```bash + mkdir -m 0700 -p ~/.homebrew + curl -L https://github.com/Homebrew/brew/tarball/master \ + | tar xz --strip 1 -C ~/.homebrew + ``` ## Installation @@ -38,6 +64,9 @@ cd ~/.dotfiles make install ``` +On a fresh macOS where `darwin-rebuild` is not yet available, nix-darwin is +bootstrapped automatically via `nix run nix-darwin -- switch`. + ## Uninstallation ```bash @@ -45,6 +74,37 @@ cd ~/.dotfiles make uninstall ``` +## Package management + +### macOS (nix-darwin) + +A single `darwin-rebuild switch` manages **everything** — CLI tools (via +home-manager), GUI applications (via Homebrew casks), and system settings: + +```bash +make run-darwin +``` + +- **CLI tools** — edit `home.nix`, then `make run-darwin` +- **GUI / cask apps** — edit `darwin.nix` (`homebrew.casks`), then + `make run-darwin` + +### Linux (home-manager) + +On Linux, only home-manager is used for CLI tools: + +```bash +make run-nix +``` + +### Ruby versions + +Ruby versions are installed via `ruby-install` (managed by Nix): + +```bash +make install-rubies +``` + ## macOS setup ### Sensible macOS defaults @@ -57,18 +117,12 @@ su - admin -c "env PATH=$PATH:/usr/sbin/ bash -x $HOME/.dotfiles/.macos" su - admin -c "env PATH=$PATH:/usr/sbin/ bash -x $HOME/.dotfiles/.macos_hardening" ``` -### Install Homebrew formulae and apps - -```bash -make run-brew -``` - ## Bash ### Set Bash as default shell ```bash -chsh -s "$(brew --prefix)/bin/bash" +chsh -s "$(command -v bash)" exec $SHELL ``` diff --git a/darwin.nix b/darwin.nix new file mode 100644 index 0000000..afb5d74 --- /dev/null +++ b/darwin.nix @@ -0,0 +1,70 @@ +{ pkgs, ... }: + +let + username = "bdossantos"; + homeDirectory = "/Users/${username}"; +in +{ + system.primaryUser = username; + + # Nix settings + nix.settings = { + experimental-features = [ + "nix-command" + "flakes" + ]; + }; + + # Homebrew — declaratively manage GUI / cask applications + homebrew = { + enable = true; + prefix = "${homeDirectory}/.homebrew"; + + onActivation = { + autoUpdate = true; + upgrade = true; + cleanup = "uninstall"; + }; + + global = { + autoUpdate = false; + }; + + caskArgs = { + appdir = "~/Applications"; + fontdir = "~/Library/Fonts"; + no_binaries = true; + require_sha = true; + }; + + brews = [ + "gcloud-cli" + ]; + + casks = [ + "1password" + "anki" + "firefox" + "font-hack" + "font-hack-nerd-font" + "font-jetbrains-mono" + "ghostty" + "google-chrome" + "gpg-suite" + "imageoptim" + "little-snitch" + "micro-snitch" + "spotify" + "tor-browser" + "vlc" + ]; + }; + + # Disable the macOS default ssh-agent + system.activationScripts.postActivation.text = '' + echo "Disabling macOS default ssh-agent..." + launchctl disable "user/$(id -u ${username})/com.openssh.ssh-agent" 2>/dev/null || true + ''; + + system.stateVersion = 5; +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..6cde50f --- /dev/null +++ b/flake.nix @@ -0,0 +1,75 @@ +{ + description = "bdossantos dotfiles — managed with nix-darwin and home-manager"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + + nix-darwin = { + url = "github:nix-community/nix-darwin"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + home-manager = { + url = "github:nix-community/home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + { + nixpkgs, + nix-darwin, + home-manager, + ... + }: + let + supportedDarwinSystems = [ + "aarch64-darwin" + "x86_64-darwin" + ]; + + supportedLinuxSystems = [ + "x86_64-linux" + "aarch64-linux" + ]; + + mkDarwinConfiguration = + system: + nix-darwin.lib.darwinSystem { + inherit system; + modules = [ + ./darwin.nix + home-manager.darwinModules.home-manager + { + home-manager.useGlobalPkgs = true; + # Keep packages in ~/.nix-profile/bin for compatibility with + # the manually managed PATH in .profile + home-manager.useUserPackages = false; + home-manager.users.bdossantos = import ./home.nix; + } + ]; + }; + + mkHomeConfiguration = + system: + home-manager.lib.homeManagerConfiguration { + pkgs = nixpkgs.legacyPackages.${system}; + modules = [ ./home.nix ]; + }; + in + { + darwinConfigurations = builtins.listToAttrs ( + map (system: { + name = system; + value = mkDarwinConfiguration system; + }) supportedDarwinSystems + ); + + homeConfigurations = builtins.listToAttrs ( + map (system: { + name = system; + value = mkHomeConfiguration system; + }) supportedLinuxSystems + ); + }; +} diff --git a/home.nix b/home.nix new file mode 100644 index 0000000..c973b17 --- /dev/null +++ b/home.nix @@ -0,0 +1,153 @@ +{ pkgs, lib, ... }: + +let + isDarwin = pkgs.stdenv.isDarwin; + isLinux = pkgs.stdenv.isLinux; + username = "bdossantos"; + homeDirectory = if isDarwin then "/Users/${username}" else "/home/${username}"; +in +{ + home.username = username; + home.homeDirectory = homeDirectory; + home.stateVersion = "24.11"; + + programs.home-manager.enable = true; + + home.packages = + with pkgs; + [ + # --- search & grep --- + ack + ripgrep + silver-searcher + ast-grep + + # --- file & directory utilities --- + coreutils + diffutils + fd + findutils + moreutils + tree + eza + entr + watch + + # --- text processing --- + gawk + gnused + gnugrep + jq + miller + ed + + # --- compression --- + advancecomp + gzip + pigz + xz + + # --- image optimisation --- + gifsicle + jhead + jpegoptim + libjpeg + optipng + pngcrush + pngquant + + # --- networking --- + curl + ipcalc + nmap + openssh + wget + + # --- shells & prompts --- + bash + bash-completion + starship + + # --- version control --- + diff-so-fancy + git + gitlint + lazygit + tig + + # --- editors --- + vim + + # --- terminal multiplexers --- + tmux + + # --- gnu replacements --- + gnutar + gnupatch + gnumake + + # --- languages & runtimes --- + go + go-jsonnet + python3 + + # --- ruby --- + chruby + ruby-build + ruby-install + + # --- linters & formatters --- + hadolint + pre-commit + nodePackages.prettier + ruff + shellcheck + shfmt + vale + editorconfig-core-c + + # --- cloud & infrastructure --- + awscli2 + k9s + kops + kubectx + packer + + # --- security & crypto --- + gnupg + openssl + + # --- misc cli tools --- + ansifilter + bat + cheat + ctags + fzf + htop + less + lesspipe + luarocks + pv + pwgen + readline + s3cmd + stow + tldr + zoxide + + # --- libraries --- + gsl + libyaml + + # --- build tools --- + bazel + gcc + ] + ++ lib.optionals isDarwin [ + terminal-notifier + ] + ++ lib.optionals isLinux [ + binutils + tcpdump + ]; +}