Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/pr-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ jobs:
- uses: actions/checkout@v4

- name: Full install
run: make
run: make basic

# Verify the install actually landed, not just that `make` exited 0.
# Verify the install actually landed, not just that `make basic` exited 0.
- name: Verify install
run: |
test -L ~/.config/nvim/init.lua
Expand Down
28 changes: 16 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
# A bare `make` asks which config sets this machine should use, then installs
# with that selection. Use `make basic` for the non-interactive equivalent.
.DEFAULT_GOAL := picky

picky:
@bash ./install-scripts/pick-sets.sh
@$(MAKE) --no-print-directory full-install

# Enable just the "basic" set and install, skipping the picker. This is what
# CI runs, and the right target for any unattended install.
basic:
@bash ./install-scripts/pick-sets.sh basic
@$(MAKE) --no-print-directory full-install

# Install using whatever sets are already enabled, without asking.
full-install:
@bash ./install-scripts/01-install-packages.sh
@bash ./install-scripts/02-move-files.sh
Expand All @@ -12,15 +27,4 @@ refresh:
@bash ./install-scripts/02-move-files.sh
@exec fish

picky:
@bash ./install-scripts/pick-sets.sh
@bash ./install-scripts/01-install-packages.sh
@bash ./install-scripts/02-move-files.sh
@fish ./install-scripts/03-fisher-install.fish
@fish ./install-scripts/04-fish-plugins.fish
@fish ./install-scripts/05-tmux-plugins.fish
@fish ./install-scripts/06-vim-setup.fish
@bash ./install-scripts/07-last-touches.sh
@exec fish

.PHONY: full-install refresh picky
.PHONY: picky basic full-install refresh
60 changes: 43 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A comprehensive dotfiles setup for a modern development environment featuring Ne
- **Fish Shell**: Lightweight `simple.fish` prompt, cross-machine history sync, aliases, and productivity functions
- **Tmux**: Custom keybindings, mouse support, and a modular status bar with toggleable gadgets
- **Cross-platform**: Supports macOS, Linux (Arch, Debian/Ubuntu, Alpine, Fedora/RHEL, Gentoo), and FreeBSD
- **Config Sets**: Pick and choose which groups of config get deployed via an interactive `make picky` dialog
- **Config Sets**: Pick and choose which groups of config get deployed via the interactive checklist a bare `make` opens
- **Automated Setup**: One-command installation via Makefile

## Quick Install
Expand All @@ -17,18 +17,28 @@ A comprehensive dotfiles setup for a modern development environment featuring Ne
make
```

The setup will automatically detect your operating system and install all necessary packages and configurations.
This opens an interactive checklist of the available config sets, then runs
the full install using your selection. The setup automatically detects your
operating system and installs all necessary packages and configurations.

To install just the `basic` set without being asked anything — the right
choice for an unattended install, and what CI runs:

```bash
make basic
```

To re-sync just the config file symlinks (without reinstalling packages or plugins):

```bash
make refresh
```

To interactively choose which config sets get deployed, then run the full install using that selection:
To re-run the full install using the sets you already picked, without the
checklist:

```bash
make picky
make full-install
```

## Repository Layout
Expand All @@ -53,15 +63,15 @@ config/
manifest
os # "Linux" — hidden from `make picky` elsewhere
apply.sh
macos/ # macOS keyboard repeat settings
macos/ # macOS system settings (`defaults write`)
description
manifest
os # "Darwin" — hidden from `make picky` elsewhere
apply.sh
# further sets live alongside these, following the same layout
install-scripts/ # Numbered setup scripts run by the Makefile
lib/sets.sh # Shared helpers for discovering/enabling sets
pick-sets.sh # `make picky` — the dialog checklist
pick-sets.sh # the dialog checklist; takes set names to skip it
scripts/ # Misc helper scripts (e.g. Gentoo kernel upgrade)
tests/ # Dockerfiles used by CI to test installs per distro
```
Expand All @@ -73,8 +83,17 @@ Config files are grouped into **sets** under `config/sets/<name>/`:
- **`basic`** — everything this repo has always deployed (Neovim, Fish, Tmux)
- **`kde`** — sets the KDE Plasma keyboard repeat rate to 50/s with a 250ms
delay (via `kwriteconfig5`/`6` on `kcminputrc`)
- **`macos`** — sets the macOS keyboard repeat rate as fast as possible with
the shortest delay (via `defaults write`)
- **`macos`** — the macOS system settings that differ from stock, applied via
`defaults write`: fastest keyboard repeat rate with the shortest delay, all
automatic text substitution off, text replacements, the input-source and
Quick Note hotkeys disabled, dark mode, a faster trackpad, seconds in the
menu bar clock, no Dock recents, the built-in drag-to-edge window tiling
off (Rectangle handles that), Finder in list view opening on `~` with a
status bar, screenshots to the clipboard, personalised ads off, and — if
passwordless `sudo` is available — a display that never sleeps. It also
installs **Rectangle** and **Maccy** via Homebrew Cask and applies their
shortcuts (Cmd+Shift+arrows for window halves, Cmd+Shift+V for clipboard
history)

Each set is independent and additive — enabling `kde` or `macos` doesn't
disturb `basic`. `kde` and `macos` each declare an `os` file restricting
Expand All @@ -83,16 +102,23 @@ them to their platform, so `make picky` only offers `kde` on Linux and
wrong OS anyway (e.g. a shared `sets.conf`), it's skipped with a warning
at deploy time instead of failing.

Run `make picky` to get an interactive checklist (space to toggle, enter to
confirm) of every set found under `config/sets/`. Confirming saves your
selection to `~/.config/dotfiles/sets.conf` and immediately kicks off the
full install (packages, plugins, config deployment, last touches) using it
— the same steps `make full-install` runs. Cancelling (Esc) leaves your
existing selection untouched and stops there, without installing anything.
A bare `make` (or `make picky`, the same target by name) gives you an
interactive checklist (space to toggle, enter to confirm) of every set found
under `config/sets/`. Confirming saves your selection to
`~/.config/dotfiles/sets.conf` and immediately kicks off the full install
(packages, plugins, config deployment, last touches) using it — the same
steps `make full-install` runs. Cancelling (Esc) leaves your existing
selection untouched and stops there, without installing anything.

`make basic` is the non-interactive equivalent: it enables just the `basic`
set and installs, skipping the checklist entirely (and skipping the `dialog`
dependency the checklist would otherwise pull in). CI uses it, and so should
any unattended install.

The saved selection is remembered by future `make refresh` / `make
full-install` runs too, so you only need to pick once per machine. If
you've never run `make picky`, everything defaults to just `basic`,
matching this repo's historical behavior.
full-install` runs too, so you only need to pick once per machine. If you've
never picked, everything defaults to just `basic`, matching this repo's
historical behavior.

### Adding a new set

Expand Down
228 changes: 223 additions & 5 deletions config/sets/macos/apply.sh
Original file line number Diff line number Diff line change
@@ -1,15 +1,233 @@
#!/bin/bash
set -euo pipefail

# Sets the macOS keyboard repeat rate as fast as possible and the initial
# repeat delay as short as possible — below the range the System Settings
# slider exposes. Log out/in (or restart affected apps) for it to fully
# take effect everywhere.
# macOS settings that diverge from stock defaults on this machine.
# Each section below is independent and safe to re-run.
#
# Some of these only take effect in apps launched afterwards; the Dock,
# Finder and SystemUIServer are restarted at the end to pick up the rest.

if [ "$(uname)" != "Darwin" ] || ! command -v defaults >/dev/null 2>&1; then
echo "Warning: not macOS (or 'defaults' unavailable); skipping keyboard repeat settings." >&2
echo "Warning: not macOS (or 'defaults' unavailable); skipping macOS settings." >&2
exit 0
fi

# --- Keyboard -------------------------------------------------------------

# Keyboard repeat rate as fast as possible and the initial repeat delay as
# short as possible — below the range the System Settings slider exposes.
defaults write NSGlobalDomain KeyRepeat -int 1
defaults write NSGlobalDomain InitialKeyRepeat -int 10

# Turn off every "helpful" text substitution (System Settings > Keyboard >
# Input Sources > Edit). They fight with writing code and shell commands.
defaults write NSGlobalDomain NSAutomaticCapitalizationEnabled -bool false
defaults write NSGlobalDomain NSAutomaticDashSubstitutionEnabled -bool false
defaults write NSGlobalDomain NSAutomaticInlinePredictionEnabled -bool false
defaults write NSGlobalDomain NSAutomaticPeriodSubstitutionEnabled -bool false
defaults write NSGlobalDomain NSAutomaticQuoteSubstitutionEnabled -bool false
defaults write NSGlobalDomain NSAutomaticSpellingCorrectionEnabled -bool false
defaults write NSGlobalDomain WebAutomaticSpellingCorrectionEnabled -bool false

# Text replacements (System Settings > Keyboard > Text Replacements).
# Note this replaces the whole list, including macOS's stock "omw" entry —
# which is why it's repeated here.
#
# These use XML plist literals rather than `defaults`' old-style `{ k = v; }`
# syntax, which types every scalar as a string — macOS wants a real integer
# in `on` and a real boolean in the hotkey dicts below.
defaults write NSGlobalDomain NSUserDictionaryReplacementItems '<array>
<dict><key>on</key><integer>1</integer><key>replace</key><string>omw</string><key>with</key><string>On my way!</string></dict>
<dict><key>on</key><integer>1</integer><key>replace</key><string>/stern</string><key>with</key><string>ಠ_ಠ</string></dict>
<dict><key>on</key><integer>1</integer><key>replace</key><string>/shrug</string><key>with</key><string>¯\_(ツ)_/¯</string></dict>
</array>'

# Free up Ctrl+Space / Ctrl+Opt+Space by disabling the "select previous /
# next input source" shortcuts (System Settings > Keyboard > Keyboard
# Shortcuts > Input Sources), and drop the Quick Note hotkey while we're
# here (Keyboard Shortcuts > Mission Control).
disable_hotkey() {
local id="$1" p1="$2" p2="$3" p3="$4"
defaults write com.apple.symbolichotkeys AppleSymbolicHotKeys -dict-add "$id" \
"<dict>
<key>enabled</key><false/>
<key>value</key><dict>
<key>parameters</key><array>
<integer>$p1</integer><integer>$p2</integer><integer>$p3</integer>
</array>
<key>type</key><string>standard</string>
</dict>
</dict>"
}
disable_hotkey 60 32 49 262144 # select the previous input source
disable_hotkey 61 32 49 786432 # select the next input source
disable_hotkey 164 65535 65535 0 # show Quick Note

# --- Appearance -----------------------------------------------------------

# Dark mode (System Settings > Appearance).
defaults write NSGlobalDomain AppleInterfaceStyle -string Dark

# Trackpad tracking speed, faster than the 0.6875 default
# (System Settings > Trackpad > Point & Click).
defaults write NSGlobalDomain com.apple.trackpad.scaling -float 1

# Show seconds in the menu bar clock (System Settings > Control Centre >
# Menu Bar Only > Clock Options).
defaults write com.apple.menuextra.clock ShowSeconds -bool true

# --- Dock -----------------------------------------------------------------

# No "Recent Applications" section in the Dock (System Settings > Desktop &
# Dock).
defaults write com.apple.dock show-recents -bool false

# --- Window tiling --------------------------------------------------------

# Disable macOS's own drag-to-edge window tiling and its margins — window
# management is Rectangle's job (System Settings > Desktop & Dock > Windows).
defaults write com.apple.WindowManager EnableTiledWindowMargins -bool false
defaults write com.apple.WindowManager EnableTilingByEdgeDrag -bool false
defaults write com.apple.WindowManager EnableTopTilingByEdgeDrag -bool false
defaults write com.apple.WindowManager EnableTilingOptionAccelerator -bool false

# --- Finder ---------------------------------------------------------------

# List view by default, new windows open on ~, and keep the status bar.
defaults write com.apple.finder FXPreferredViewStyle -string Nlsv
defaults write com.apple.finder NewWindowTarget -string PfHm
defaults write com.apple.finder ShowStatusBar -bool true

# --- Screenshots ----------------------------------------------------------

# Screenshots go to the clipboard rather than the Desktop. `target` is the
# older key, `target-screenshot` the one Sonoma and later actually read.
defaults write com.apple.screencapture target -string clipboard
defaults write com.apple.screencapture target-screenshot -string clipboard

# --- Privacy --------------------------------------------------------------

# Opt out of Apple's personalised advertising (System Settings > Privacy &
# Security > Apple Advertising).
defaults write com.apple.AdLib allowApplePersonalizedAdvertising -bool false

# --- Apps -----------------------------------------------------------------

# Rectangle (window management) and Maccy (clipboard history), plus the
# shortcuts and options they're configured with here.
#
# Both apps write their whole preference domain back out when they quit, so
# a `defaults write` against a running app gets silently clobbered. Each one
# is therefore quit first, configured, and only relaunched if it was already
# running.

install_cask() {
local cask="$1"
if ! command -v brew >/dev/null 2>&1; then
echo "Warning: Homebrew not found; skipping '$cask'." >&2
return 1
fi
brew list --cask "$cask" >/dev/null 2>&1 || brew install --cask "$cask"
}

app_is_running() {
pgrep -qx "$1" 2>/dev/null
}

quit_app() {
app_is_running "$1" || return 0
osascript -e "quit app \"$1\"" >/dev/null 2>&1 || killall "$1" >/dev/null 2>&1 || true
# Give the app a moment to flush its preferences before we overwrite them.
for _ in 1 2 3 4 5 6 7 8 9 10; do
app_is_running "$1" || break
sleep 0.5
done
}

# Rectangle shortcuts are stored as { keyCode, modifierFlags } dictionaries;
# XML plist literals keep both as real integers.
rectangle_shortcut() {
defaults write com.knollsoft.Rectangle "$1" \
"<dict>
<key>keyCode</key><integer>$2</integer>
<key>modifierFlags</key><integer>$3</integer>
</dict>"
}

if install_cask rectangle; then
rectangle_was_running=false
if app_is_running Rectangle; then
rectangle_was_running=true
fi
quit_app Rectangle

# 1179648 = Command + Shift, 786432 = Control + Option.
rectangle_shortcut leftHalf 123 1179648 # Cmd+Shift+Left
rectangle_shortcut rightHalf 124 1179648 # Cmd+Shift+Right
rectangle_shortcut maximize 126 1179648 # Cmd+Shift+Up
rectangle_shortcut center 125 1179648 # Cmd+Shift+Down
rectangle_shortcut toggleTodo 11 786432 # Ctrl+Opt+B
rectangle_shortcut reflowTodo 45 786432 # Ctrl+Opt+N

# Accept shortcuts macOS would otherwise reserve, keep the menu bar
# clear, and don't nag about updates (the cask handles those).
defaults write com.knollsoft.Rectangle allowAnyShortcut -bool true
defaults write com.knollsoft.Rectangle hideMenubarIcon -bool true
defaults write com.knollsoft.Rectangle subsequentExecutionMode -int 0
defaults write com.knollsoft.Rectangle windowSnapping -int 1
defaults write com.knollsoft.Rectangle SUEnableAutomaticChecks -bool false

if [ "$rectangle_was_running" = true ]; then
open -a Rectangle || true
else
echo "Note: Rectangle needs Accessibility permission on first launch" >&2
echo " (System Settings > Privacy & Security > Accessibility)." >&2
fi
fi

if install_cask maccy; then
maccy_was_running=false
if app_is_running Maccy; then
maccy_was_running=true
fi
quit_app Maccy

# Maccy stores shortcuts as JSON strings of Carbon key/modifier codes.
# 768 = cmdKey + shiftKey, 2048 = optionKey.
defaults write org.p0deje.Maccy KeyboardShortcuts_popup \
-string '{"carbonKeyCode":9,"carbonModifiers":768}' # Cmd+Shift+V
defaults write org.p0deje.Maccy KeyboardShortcuts_pin \
-string '{"carbonKeyCode":35,"carbonModifiers":2048}' # Opt+P
defaults write org.p0deje.Maccy KeyboardShortcuts_delete \
-string '{"carbonKeyCode":51,"carbonModifiers":2048}' # Opt+Delete

# Open the history under the menu bar item rather than at the pointer.
defaults write org.p0deje.Maccy popupPosition -string statusItem
defaults write org.p0deje.Maccy windowSize -string '[450,800]'
defaults write org.p0deje.Maccy SUEnableAutomaticChecks -bool false

if [ "$maccy_was_running" = true ]; then
open -a Maccy || true
fi
fi

# --- Power ----------------------------------------------------------------

# Never blank the display when idle, on battery or mains — same choice as
# the kde set makes. Needs root, so it's skipped rather than prompting when
# passwordless sudo isn't available.
if command -v pmset >/dev/null 2>&1 && sudo -n true 2>/dev/null; then
sudo -n pmset -b displaysleep 0
sudo -n pmset -c displaysleep 0
else
echo "Note: skipping 'pmset displaysleep 0' (needs sudo). Run manually:" >&2
echo " sudo pmset -b displaysleep 0 && sudo pmset -c displaysleep 0" >&2
fi

# --- Apply ----------------------------------------------------------------

# Restart the affected agents so the settings above show up now instead of
# at next login. Finder windows are reopened by Finder itself.
for app in Dock Finder SystemUIServer; do
killall "$app" >/dev/null 2>&1 || true
done
Loading
Loading