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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,6 @@ build/
personal/
private/
local/

# Tmux Plugin Manager installs plugin checkouts here; do not track nested repos.
tmux/plugins/
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ make install

- **Shell Configuration**: Zsh with oh-my-zsh, custom aliases and functions
- **Editor Setup**: Neovim-only configuration with lazy.nvim plugin management
- **Terminal Multiplexer**: Tmux configuration with TPM plugin management
- **Git Configuration**: Custom aliases and templates for efficient workflows
- **Package Management**: Comprehensive Brewfile for development tools
- **Security**: Proper secrets management and validation
Expand Down Expand Up @@ -61,6 +62,7 @@ dotfiles/
├── git/ # Git templates and hooks
├── gitconfig # Git configuration
├── nvim/ # Neovim configuration
├── tmux/ # Tmux configuration
├── zshrc # Zsh configuration
├── Brewfile # Package management
└── Makefile # Build automation
Expand Down Expand Up @@ -156,6 +158,19 @@ nvim
nvim -c "Lazy install" -c "qa"
```

## 🧩 Tmux plugin setup

The installer bootstraps [TPM](https://github.com/tmux-plugins/tpm) into
`~/.config/tmux/plugins/tpm`. Other tmux plugins are declared in `tmux/tmux.conf`
and installed by TPM.

```bash
# After installation, reload tmux config
tmux source-file ~/.config/tmux/tmux.conf

# Then press prefix + I inside tmux to install declared plugins
```

### Key improvements
- Better plugin support and performance
- Asynchronous processing
Expand Down Expand Up @@ -206,6 +221,9 @@ brew bundle --file=Brewfile
# Update plugins
nvim -c "Lazy update" -c "qa"

# Update tmux plugins
# Press prefix + U inside tmux

# Run health checks
make check
```
Expand Down
4 changes: 4 additions & 0 deletions bash_profile
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,7 @@ eval "$(pyenv virtualenv-init -)"
complete -C /opt/homebrew/bin/vault vault

. "$HOME/.local/bin/env"

#THIS MUST BE AT THE END OF THE FILE FOR SDKMAN TO WORK!!!
export SDKMAN_DIR="/Users/gsantovena/.sdkman"
[[ -s "/Users/gsantovena/.sdkman/bin/sdkman-init.sh" ]] && source "/Users/gsantovena/.sdkman/bin/sdkman-init.sh"
4 changes: 4 additions & 0 deletions ghostty/config
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ font-size = 18
window-width = 120
window-height = 40
macos-option-as-alt = true
link-url = true

# Send Shift+Enter as CSI-u so TUIs such as Codex can distinguish it from Enter.
keybind = shift+enter=csi:13;2u
5 changes: 5 additions & 0 deletions nvim/lua/config/keymaps.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,8 @@ keymap("n", "<leader>qc", "<cmd>cclose<CR>")
keymap("n", "<space>", "za")

keymap("c", "w!!", "execute 'silent! write !sudo tee % >/dev/null' <bar> edit!")

keymap("n", "<C-h>", "<cmd>TmuxNavigateLeft<CR>")
keymap("n", "<C-l>", "<cmd>TmuxNavigateRight<CR>")
keymap("n", "<C-j>", "<cmd>TmuxNavigateDown<CR>")
keymap("n", "<C-k>", "<cmd>TmuxNavigateUp<CR>")
3 changes: 3 additions & 0 deletions nvim/lua/plugins/ui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ end

return {
-- UI and appearance
{
"christoomey/vim-tmux-navigator",
},
{
"nvim-lualine/lualine.nvim",
config = setup_evil_lualine,
Expand Down
68 changes: 62 additions & 6 deletions scripts/install-enhanced.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ HISTORY_LOGS=${HOME}/.logs
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOTFILES_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
HOME_FILES="bash_profile aliases exports functions git gitconfig zshrc screenrc"
CONFIG_FILES="nvim ghostty"
CONFIG_FILES="nvim ghostty tmux"
BACKUP_DIR="${HOME}/.dotfiles-backup-$(date +%Y%m%d_%H%M%S)"
TPM_REPO_URL="${TPM_REPO_URL:-https://github.com/tmux-plugins/tpm.git}"
TPM_INSTALL_DIR="${TPM_INSTALL_DIR:-$HOME/.config/tmux/plugins/tpm}"

# Function to print colored output
print_status() {
Expand Down Expand Up @@ -282,7 +284,8 @@ verify_installation() {
for file in $HOME_FILES; do
local target="$HOME/.$file"
if [ -L "$target" ]; then
local link_target=$(readlink "$target")
local link_target
link_target=$(readlink "$target")
if [ "$link_target" = "$DOTFILES_DIR/$file" ]; then
if [ "$VERBOSE" = true ]; then
print_status "$GREEN" "✅ $target correctly linked"
Expand All @@ -301,7 +304,8 @@ verify_installation() {
for file in $CONFIG_FILES; do
local target="$HOME/.config/$file"
if [ -L "$target" ]; then
local link_target=$(readlink "$target")
local link_target
link_target=$(readlink "$target")
if [ "$link_target" = "$DOTFILES_DIR/$file" ]; then
if [ "$VERBOSE" = true ]; then
print_status "$GREEN" "✅ $target correctly linked"
Expand All @@ -324,6 +328,53 @@ verify_installation() {
fi
}

# Bootstrap Tmux Plugin Manager so it can install plugins declared in tmux.conf
install_tpm() {
print_status "$BLUE" "Installing Tmux Plugin Manager..."

if [ "$DRY_RUN" = true ]; then
if [ -e "$TPM_INSTALL_DIR" ]; then
echo "TPM already exists at: $TPM_INSTALL_DIR"
else
echo "Would install TPM from $TPM_REPO_URL to: $TPM_INSTALL_DIR"
fi
return 0
fi

case "${DOTFILES_SKIP_TPM_BOOTSTRAP:-false}" in
1|true|TRUE|yes|YES)
print_status "$YELLOW" "Skipping TPM bootstrap because DOTFILES_SKIP_TPM_BOOTSTRAP is set"
return 0
;;
esac

if [ -x "$TPM_INSTALL_DIR/tpm" ]; then
print_status "$GREEN" "✅ TPM already installed at: $TPM_INSTALL_DIR"
return 0
fi

if [ -e "$TPM_INSTALL_DIR" ]; then
print_status "$RED" "❌ TPM install path exists but does not contain an executable tpm script: $TPM_INSTALL_DIR"
print_status "$YELLOW" " Remove the path or install TPM there manually, then re-run the installer."
return 1
fi

mkdir -p "$(dirname "$TPM_INSTALL_DIR")"

if [ "$VERBOSE" = true ]; then
echo "Cloning TPM: $TPM_REPO_URL --> $TPM_INSTALL_DIR"
fi

git clone --depth 1 "$TPM_REPO_URL" "$TPM_INSTALL_DIR"

if [ -x "$TPM_INSTALL_DIR/tpm" ]; then
print_status "$GREEN" "✅ TPM installed successfully"
else
print_status "$RED" "❌ TPM clone completed, but expected executable was not found: $TPM_INSTALL_DIR/tpm"
return 1
fi
}

# Print next steps
print_next_steps() {
print_status "$BLUE" "Next steps:"
Expand All @@ -333,14 +384,18 @@ print_next_steps() {
echo " # or run non-interactively:"
echo " nvim -c 'Lazy install' -c 'qa'"
echo ""
echo "2. Install Homebrew packages:"
echo "2. Install tmux plugins with TPM:"
echo " tmux source-file ~/.config/tmux/tmux.conf"
echo " # Then press prefix + I inside tmux"
echo ""
echo "3. Install Homebrew packages:"
echo " brew bundle --file=$DOTFILES_DIR/Brewfile"
echo ""
echo "3. Restart your shell or run:"
echo "4. Restart your shell or run:"
echo " source ~/.zshrc"
echo ""
if [ "$CREATE_BACKUP" = true ] && [ -d "$BACKUP_DIR" ]; then
echo "4. Your original files are backed up in:"
echo "5. Your original files are backed up in:"
echo " $BACKUP_DIR"
echo ""
fi
Expand All @@ -359,6 +414,7 @@ main() {
create_backup
install_dotfiles
verify_installation
install_tpm

echo "================================="

Expand Down
3 changes: 2 additions & 1 deletion scripts/security-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ check_permissions() {
local bad_permissions=()
while IFS= read -r -d '' file; do
if [ -f "$file" ]; then
local perms=$(stat -c "%a" "$file" 2>/dev/null || stat -f "%Lp" "$file" 2>/dev/null)
local perms
perms=$(stat -c "%a" "$file" 2>/dev/null || stat -f "%Lp" "$file" 2>/dev/null)
if [ "${perms: -1}" = "7" ] || [ "${perms: -2:1}" = "7" ]; then
bad_permissions+=("$file:$perms")
fi
Expand Down
21 changes: 18 additions & 3 deletions scripts/test-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ setup_test_env() {
echo "# Original git metadata" > "$TEST_HOME/.git/config"
mkdir -p "$TEST_HOME/.config/nvim"
echo '" Original Neovim config' > "$TEST_HOME/.config/nvim/init.vim"
mkdir -p "$TEST_HOME/.config/tmux"
echo "# Original tmux config" > "$TEST_HOME/.config/tmux/tmux.conf"
print_status "$GREEN" "Test environment created at: $TEST_HOME"
}

Expand All @@ -63,7 +65,7 @@ test_script_syntax() {
test_dry_run() {
print_status "$BLUE" "Testing dry-run installation..."

if env HOME="$TEST_HOME" bash "$INSTALL_SCRIPT" --dry-run --verbose; then
if env HOME="$TEST_HOME" DOTFILES_SKIP_TPM_BOOTSTRAP=true bash "$INSTALL_SCRIPT" --dry-run --verbose; then
print_status "$GREEN" "✅ Dry-run installation completed successfully"
else
print_status "$RED" "❌ Dry-run installation failed"
Expand All @@ -85,7 +87,7 @@ test_actual_install() {
mkdir -p "$TEST_HOME/.logs"

# Run the actual install script
if bash "$INSTALL_SCRIPT" --backup --verbose; then
if DOTFILES_SKIP_TPM_BOOTSTRAP=true bash "$INSTALL_SCRIPT" --backup --verbose; then
print_status "$GREEN" "✅ Installation completed successfully"

# Verify some key symlinks were created
Expand Down Expand Up @@ -136,9 +138,21 @@ test_actual_install() {
verification_failed=true
fi

if [ -L "$TEST_HOME/.config/tmux" ]; then
print_status "$GREEN" "✅ .config/tmux symlink created"
else
print_status "$RED" "❌ .config/tmux symlink missing"
verification_failed=true
fi

if [ -L "$TEST_HOME/.config/tmux/tmux" ]; then
print_status "$RED" "❌ Nested .config/tmux/tmux symlink should not be created"
verification_failed=true
fi

local backup_dir
backup_dir=$(find "$TEST_HOME" -maxdepth 1 -type d -name ".dotfiles-backup-*" | head -n 1)
if [ -n "$backup_dir" ] && [ -f "$backup_dir/git/config" ] && [ -f "$backup_dir/.config/nvim/init.vim" ]; then
if [ -n "$backup_dir" ] && [ -f "$backup_dir/git/config" ] && [ -f "$backup_dir/.config/nvim/init.vim" ] && [ -f "$backup_dir/.config/tmux/tmux.conf" ]; then
print_status "$GREEN" "✅ Existing directories moved to backup before linking"
else
print_status "$RED" "❌ Existing directory backup missing"
Expand Down Expand Up @@ -199,6 +213,7 @@ test_config_syntax() {
}

# Cleanup test environment
# shellcheck disable=SC2329 # Invoked by the EXIT trap in main.
cleanup() {
print_status "$BLUE" "Cleaning up test environment..."
if [ -n "${TEST_HOME:-}" ] && [ -d "$TEST_HOME" ]; then
Expand Down
87 changes: 86 additions & 1 deletion tests/test_dotfiles.bats
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,99 @@ teardown() {
echo "# Original git metadata" > "$TEST_TMPDIR/enhanced-home/.git/config"
mkdir -p "$TEST_TMPDIR/enhanced-home/.config/nvim"
echo '" Original Neovim config' > "$TEST_TMPDIR/enhanced-home/.config/nvim/init.vim"
mkdir -p "$TEST_TMPDIR/enhanced-home/.config/tmux"
echo "# Original tmux config" > "$TEST_TMPDIR/enhanced-home/.config/tmux/tmux.conf"

run env HOME="$TEST_TMPDIR/enhanced-home" bash "$DOTFILES_DIR/scripts/install-enhanced.sh" --backup
run env HOME="$TEST_TMPDIR/enhanced-home" DOTFILES_SKIP_TPM_BOOTSTRAP=true bash "$DOTFILES_DIR/scripts/install-enhanced.sh" --backup
[ "$status" -eq 0 ]
[ -L "$TEST_TMPDIR/enhanced-home/.git" ]
[ -L "$TEST_TMPDIR/enhanced-home/.config/nvim" ]
[ -L "$TEST_TMPDIR/enhanced-home/.config/ghostty" ]
[ -L "$TEST_TMPDIR/enhanced-home/.config/tmux" ]
[ ! -L "$TEST_TMPDIR/enhanced-home/.git/git" ]
[ ! -L "$TEST_TMPDIR/enhanced-home/.config/nvim/nvim" ]
[ ! -L "$TEST_TMPDIR/enhanced-home/.config/tmux/tmux" ]
}

@test "installer dry-run announces TPM bootstrap" {
run env HOME="$TEST_TMPDIR/dry-home" bash "$DOTFILES_DIR/scripts/install-enhanced.sh" --dry-run
[ "$status" -eq 0 ]
[[ "$output" == *"Would install TPM from https://github.com/tmux-plugins/tpm.git"* ]]
}

@test "installer bootstraps TPM into configured plugin directory" {
local fake_bin="$TEST_TMPDIR/fake-bin"
local tpm_dir="$TEST_TMPDIR/tpm"
local real_git
real_git="$(command -v git)"
mkdir -p "$fake_bin"

cat > "$fake_bin/git" <<EOF
#!/bin/bash
set -euo pipefail

if [ "\${1:-}" = "clone" ]; then
shift
if [ "\${1:-}" = "--depth" ]; then
shift 2
fi
repo="\$1"
target="\$2"
mkdir -p "\$target"
printf '#!/bin/sh\n' > "\$target/tpm"
chmod +x "\$target/tpm"
printf '%s\n' "\$repo" > "\$target/cloned-from"
exit 0
fi

exec "$real_git" "\$@"
EOF
chmod +x "$fake_bin/git"

run env PATH="$fake_bin:$PATH" HOME="$TEST_TMPDIR/bootstrap-home" TPM_INSTALL_DIR="$tpm_dir" bash "$DOTFILES_DIR/scripts/install-enhanced.sh" --backup
[ "$status" -eq 0 ]
[ -x "$tpm_dir/tpm" ]
[ "$(cat "$tpm_dir/cloned-from")" = "https://github.com/tmux-plugins/tpm.git" ]
}

@test "tmux config uses TPM and ignores installed plugin checkouts" {
grep -q "@plugin 'tmux-plugins/tpm'" "$DOTFILES_DIR/tmux/tmux.conf"
grep -q "run '~/.config/tmux/plugins/tpm/tpm'" "$DOTFILES_DIR/tmux/tmux.conf"
grep -q '^tmux/plugins/$' "$DOTFILES_DIR/.gitignore"
}

@test "tmux preserves Shift+Enter for Codex multiline input" {
grep -q 'terminal-features.*,xterm-ghostty:extkeys:hyperlinks' "$DOTFILES_DIR/tmux/tmux.conf"
grep -q '^set-option -g extended-keys always$' "$DOTFILES_DIR/tmux/tmux.conf"
grep -q '^set-option -g extended-keys-format csi-u$' "$DOTFILES_DIR/tmux/tmux.conf"
grep -q '^bind-key -n S-Enter send-keys -H 1b 5b 31 33 3b 32 75$' "$DOTFILES_DIR/tmux/tmux.conf"
grep -q '^keybind = shift+enter=csi:13;2u$' "$DOTFILES_DIR/ghostty/config"
}

@test "tmux and ghostty preserve clickable URLs" {
grep -q 'terminal-features.*,xterm-ghostty:extkeys:hyperlinks' "$DOTFILES_DIR/tmux/tmux.conf"
grep -q '^bind-key -n MouseDown1Pane if -F "#{mouse_hyperlink}"' "$DOTFILES_DIR/tmux/tmux.conf"
grep -q 'command -v open' "$DOTFILES_DIR/tmux/tmux.conf"
grep -q 'command -v xdg-open' "$DOTFILES_DIR/tmux/tmux.conf"
grep -q '^link-url = true$' "$DOTFILES_DIR/ghostty/config"
}

@test "tmux config syntax is valid" {
if ! command -v tmux >/dev/null 2>&1; then
skip "tmux not available"
fi

run tmux -f /dev/null source-file -n "$DOTFILES_DIR/tmux/tmux.conf"
[ "$status" -eq 0 ]
}

@test "ghostty config syntax is valid when ghostty is available" {
if ! command -v ghostty >/dev/null 2>&1; then
skip "ghostty not available"
fi

run ghostty +validate-config --config-file="$DOTFILES_DIR/ghostty/config"
[ "$status" -eq 0 ]
}

@test "required dotfiles exist" {
Expand Down
Loading
Loading