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
184 changes: 184 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#!/usr/bin/env bash
#
# Pre-commit hook for dotfiles repository
# Validates shell scripts before committing
#

set -e

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'

echo "Running pre-commit checks..."

# Get list of staged files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)

ERRORS=0

#############################################################################
# Check bash scripts with shellcheck
#############################################################################

check_shellcheck() {
if ! command -v shellcheck &>/dev/null; then
echo -e "${YELLOW}Warning: shellcheck not installed, skipping lint${NC}"
return 0
fi

local bash_files=""
# These are zsh scripts, skip shellcheck -s bash for them
local zsh_scripts="bin/dotfiles-test bin/dotfiles-doctor bin/dotfiles-cheatsheet bin/dotfiles-profiler"

for file in $STAGED_FILES; do
case "$file" in
bin/dotfiles|bin/dotfiles-*|scripts/*.sh|macos/*.sh)
if [[ -f "$file" ]]; then
# Skip zsh scripts from bash shellcheck
local is_zsh=false
for zf in $zsh_scripts; do
[[ "$file" == "$zf" ]] && is_zsh=true
done
$is_zsh || bash_files="$bash_files $file"
fi
;;
esac
done

if [[ -n "$bash_files" ]]; then
echo "Checking bash scripts with shellcheck..."
for file in $bash_files; do
if ! shellcheck -e SC1090,SC1091,SC2034,SC2119,SC2154 -s bash "$file" 2>/dev/null; then
echo -e "${RED}✗ Shellcheck failed: $file${NC}"
((ERRORS++))
else
echo -e "${GREEN}✓ $file${NC}"
fi
done
fi
}

#############################################################################
# Check bash syntax
#############################################################################

check_bash_syntax() {
# These are zsh scripts, skip bash -n for them
local zsh_scripts="bin/dotfiles-test bin/dotfiles-doctor bin/dotfiles-cheatsheet bin/dotfiles-profiler"
local bash_files=""
for file in $STAGED_FILES; do
case "$file" in
bin/dotfiles|bin/dotfiles-*|scripts/*.sh|macos/*.sh)
if [[ -f "$file" ]]; then
local is_zsh=false
for zf in $zsh_scripts; do
[[ "$file" == "$zf" ]] && is_zsh=true
done
$is_zsh || bash_files="$bash_files $file"
fi
;;
esac
done

if [[ -n "$bash_files" ]]; then
echo "Checking bash syntax..."
for file in $bash_files; do
if ! bash -n "$file" 2>/dev/null; then
echo -e "${RED}✗ Syntax error: $file${NC}"
((ERRORS++))
fi
done
fi
}

#############################################################################
# Check zsh syntax
#############################################################################

check_zsh_syntax() {
if ! command -v zsh &>/dev/null; then
echo -e "${YELLOW}Warning: zsh not installed, skipping zsh syntax check${NC}"
return 0
fi

local zsh_files=""
for file in $STAGED_FILES; do
case "$file" in
runcom/.*|system/.*|bin/dotfiles-test|bin/dotfiles-doctor)
if [[ -f "$file" ]]; then
zsh_files="$zsh_files $file"
fi
;;
esac
done

if [[ -n "$zsh_files" ]]; then
echo "Checking zsh syntax..."
for file in $zsh_files; do
if ! zsh -n "$file" 2>/dev/null; then
echo -e "${RED}✗ Syntax error: $file${NC}"
((ERRORS++))
fi
done
fi
}

#############################################################################
# Check for secrets
#############################################################################

check_secrets() {
echo "Checking for potential secrets..."

local secret_patterns=(
"PRIVATE.KEY"
"BEGIN RSA PRIVATE KEY"
"BEGIN DSA PRIVATE KEY"
"BEGIN EC PRIVATE KEY"
"BEGIN OPENSSH PRIVATE KEY"
"password.*=.*['\"]"
"secret.*=.*['\"]"
"api_key.*=.*['\"]"
"apikey.*=.*['\"]"
"access_token.*=.*['\"]"
)

for file in $STAGED_FILES; do
# Skip files that legitimately contain pattern strings (not actual secrets)
case "$file" in
.githooks/pre-commit|bin/dotfiles-test|bin/dotfiles-secrets) continue ;;
esac
if [[ -f "$file" ]]; then
for pattern in "${secret_patterns[@]}"; do
if grep -qiE "$pattern" "$file" 2>/dev/null; then
echo -e "${RED}✗ Potential secret found in: $file${NC}"
echo " Pattern: $pattern"
((ERRORS++))
fi
done
fi
done
}

#############################################################################
# Main
#############################################################################

check_bash_syntax
check_zsh_syntax
check_shellcheck
check_secrets

if [[ $ERRORS -gt 0 ]]; then
echo ""
echo -e "${RED}Pre-commit check failed with $ERRORS error(s)${NC}"
echo "Fix the issues above and try again."
echo "To skip this check (not recommended): git commit --no-verify"
exit 1
fi

echo -e "${GREEN}All pre-commit checks passed!${NC}"
exit 0
128 changes: 128 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: CI

on:
push:
branches: [main, master]
pull_request:
branches: [main, master]

jobs:
lint:
name: Lint Shell Scripts
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive

- name: Install shellcheck
run: sudo apt-get install -y shellcheck

- name: Run shellcheck on bash scripts
run: |
shellcheck -e SC1090,SC1091,SC2034,SC2119,SC2154 -s bash \
bin/dotfiles \
bin/dotfiles-secrets \
bin/dotfiles-setup \
scripts/*.sh \
macos/*.sh

- name: Check bash syntax
run: |
for file in bin/dotfiles scripts/*.sh macos/*.sh; do
if [[ -f "$file" ]]; then
bash -n "$file" || exit 1
fi
done

syntax:
name: Validate Zsh Syntax
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive

- name: Install zsh
run: sudo apt-get install -y zsh

- name: Check zsh syntax
run: |
files=(
runcom/.zshrc
runcom/.zprofile
runcom/.zlogin
runcom/.profile
runcom/.zpreztorc
system/.alias
system/.bindings
system/.completion
system/.env
system/.fix
system/.fnm
system/.function
system/.function_fs
system/.function_fun
system/.function_network
system/.function_text
system/.fzf
system/.grep
system/.path
system/.pnpm
system/.starship
system/.zoxide
bin/dotfiles-test
bin/dotfiles-doctor
bin/dotfiles-cheatsheet
bin/dotfiles-profiler
)

for file in "${files[@]}"; do
if [[ -f "$file" ]]; then
echo "Checking $file..."
zsh -n "$file" || exit 1
fi
done

test:
name: Run Tests
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive

- name: Setup Homebrew
uses: Homebrew/actions/setup-homebrew@cced187498280712e078aaba62dc13a3e9cd80bf

- name: Install dependencies
run: |
brew install shellcheck coreutils

- name: Run test suite
run: |
chmod +x bin/dotfiles-test
./bin/dotfiles-test --quick
env:
DOTFILES_DIR: ${{ github.workspace }}

brewfile:
name: Validate Brewfile
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Homebrew
uses: Homebrew/actions/setup-homebrew@cced187498280712e078aaba62dc13a3e9cd80bf

- name: Validate Brewfile syntax
run: |
# Just check that the Brewfile is valid syntax
brew bundle check --file=Brewfile 2>&1 || true
# The check will "fail" because packages aren't installed
# but syntax errors would cause a different error
brew bundle list --file=Brewfile > /dev/null
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,9 @@ runcom/.vim/.netrwhist
runcom/.vim/bundle/*
!runcom/.vim/bundle/.gitignore
!runcom/.vim/bundle/Vundle.vim

# Machine-specific profile (contains secrets/local config)
profiles/local.zsh

# Brewfile lock (optional - can be committed for reproducibility)
Brewfile.lock.json
7 changes: 2 additions & 5 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
[submodule "apps/terminal/nord_theme"]
path = apps/terminal/nord_theme
url = https://github.com/arcticicestudio/nord-terminal-app
url = https://github.com/nordtheme/terminal-app
[submodule "apps/xcode/nord_theme"]
path = apps/xcode/nord_theme
url = https://github.com/arcticicestudio/nord-xcode
url = https://github.com/nordtheme/xcode
[submodule "modules/prezto"]
path = modules/prezto
url = https://github.com/sorin-ionescu/prezto.git
[submodule "modules/zsh/zsh-autocomplete"]
path = modules/zsh/zsh-autocomplete
url = https://github.com/marlonrichert/zsh-autocomplete.git
[submodule "runcom/.vim/bundle/Vundle.vim"]
path = runcom/.vim/bundle/Vundle.vim
url = https://github.com/VundleVim/Vundle.vim.git
Expand Down
36 changes: 36 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# AGENTS.md

macOS dotfiles repo — bootstraps a machine with system defaults, dev tools, apps, and shell config.

## Essentials

- **Language**: zsh (shell config), bash (install scripts)
- **Entry point**: `./bin/dotfiles <command>` — run `./bin/dotfiles help` for full reference
- **Test**: `./bin/dotfiles test` (must pass before committing)
- **Lint**: shellcheck (runs in CI and pre-commit hooks)
- **CI**: GitHub Actions on push/PR — syntax validation, shellcheck, tests, Brewfile validation
- **Link method**: GNU Stow — `runcom/` stows to `~/`, `config/` stows to `~/.config/`

## Critical Context

These affect nearly every task:

- **Submodules**: External deps (Prezto, hosts, zsh plugins) live in `modules/`. Always `git submodule update --init --recursive` after clone.
- **Intel + Apple Silicon**: Both supported. Homebrew prefix is `/opt/homebrew` (AS) or `/usr/local` (Intel). Use `bin/is-apple-silicon` to detect.
- **Prezto, not Oh My Zsh**: Shell framework is Prezto for performance. Prompt is Powerlevel10k via Prezto's prompt module. Starship config exists in `system/.starship` but is **not sourced**.
- **FNM, not NVM**: Node.js managed by FNM (Fast Node Manager). Use `require_fnm()` and `source_fnm()`.
- **Packages**: Brewfile for brew/cask/mas (`brew bundle install`). `.list` files in `packages/` for npm, vscode extensions, and other tools Brewfile doesn't support. See [packages.md](docs/agents/packages.md).
- **Backups**: Dotfile linking backs up originals to `~/.dotfiles_backup/<timestamp>` before replacing.

## Detail Docs

| Topic | File |
| --------------------------------------------- | -------------------------------------------------- |
| Directory structure, Stow linking, submodules | [architecture.md](docs/agents/architecture.md) |
| CLI commands and common workflows | [commands.md](docs/agents/commands.md) |
| Zsh config, Prezto, profiles, caching, PATH | [shell-config.md](docs/agents/shell-config.md) |
| Package management (Brewfile + list files) | [packages.md](docs/agents/packages.md) |
| macOS system defaults and Dock | [macos-defaults.md](docs/agents/macos-defaults.md) |
| Test suite, CI, git hooks | [testing-and-ci.md](docs/agents/testing-and-ci.md) |
| Secrets management (macOS Keychain) | [secrets.md](docs/agents/secrets.md) |
| Neovim config (lazy.nvim) | [neovim.md](docs/agents/neovim.md) |
Loading