Skip to content
Open
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 .gitconfig
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ email = arjun810@gmail.com
default = current
[alias]
serve = daemon --verbose --export-all --base-path=.git --reuseaddr --strict-paths .git/

[includeIf "gitdir:~/work/"]
path = ~/.gitconfig.work
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: CI

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

jobs:
shellcheck:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install ShellCheck
run: sudo apt-get update && sudo apt-get install -y shellcheck
- name: Run ShellCheck
run: shellcheck init_dotfiles.sh cleanup_dotfiles.sh || true

brew-bundle-check:
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Brew bundle check
run: brew bundle check --file osx/Brewfile || true
2 changes: 2 additions & 0 deletions .zshrc
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,5 @@ function textme() {
source ~/.secrets.env

. /opt/homebrew/opt/asdf/asdf.sh

[ -f ~/.zshrc.local ] && . ~/.zshrc.local
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
Dotfiles Setup

Prerequisites
- macOS (tested), zsh
- curl, git, Ruby or system Ruby for running setup.rb

Bootstrap on macOS
- Run: ruby osx/setup.rb
- The script will:
- Install Homebrew non-interactively and add zsh to /etc/shells safely
- Install Brewfile packages and casks
- Clone or reuse ~/.dotfiles
- Initialize symlinks with backups and XDG config shims
- Install vim-plug and Zim, then install Vim plugins
- Install asdf Ruby, set global version, and install bundler via asdf exec
- Install pipx and ensure PATH
- Optionally install Phoenix generator if Elixir is available
- Print notes for manual steps like SSH keys, iTerm schemes, and font changes

Manual steps
- Generate SSH keys if missing: ssh-keygen -t ed25519 -C "you@example.com"
- iTerm2: import color schemes from osx/*.itermcolors; set a Powerline/Nerd Font
- Change terminal font to Monaco for Powerline or Meslo Nerd Font

Updating packages
- Brewfile: edit osx/Brewfile; run brew bundle --file osx/Brewfile
- Vim plugins: update .vimrc and run :PlugUpdate
- Zsh modules: edit .zimrc and run zimfw update

Per-host configuration
- Add ~/.zshrc.local for host-specific shell settings
- Git can include ~/.gitconfig.work via includeIf

Dotfile managers (optional)
- For selective deployment or managing across multiple machines consider:
- GNU Stow: create directories per app and stow them into $HOME
- yadm: git-based dotfile manager with templating and bootstrap hooks
- chezmoi: cross-platform manager with encryption support and templates
- This repo currently uses simple symlinks via init_dotfiles.sh; you can migrate gradually by structuring files for stow or adopting chezmoi/yadm.

Uninstall/cleanup (manual for now)
- Remove symlinks in $HOME
- Restore backups with .bak.TIMESTAMP suffix created by init_dotfiles.sh
22 changes: 22 additions & 0 deletions cleanup_dotfiles.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail

DOTDIR="${HOME}/.dotfiles"
FILES=(.vimrc .zshrc .zshenv .zlogin .zimrc .gemrc .gitconfig .tool-versions)

for file in "${FILES[@]}"; do
target="${HOME}/${file}"
if [ -L "$target" ]; then
rm -f "$target"
fi
# restore latest backup if exists
latest_backup=$(ls -1t ${target}.bak.* 2>/dev/null | head -n1 || true)
if [ -n "${latest_backup}" ]; then
mv "${latest_backup}" "$target"
fi
done

# XDG config shims
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-${HOME}/.config}"
rm -f "${XDG_CONFIG_HOME}/vim/vimrc" || true
rm -f "${XDG_CONFIG_HOME}/git/config" || true
48 changes: 35 additions & 13 deletions init_dotfiles.sh
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
#! /bin/bash

pushd ~
ln -s .dotfiles/.vimrc ~/.vimrc
ln -s .dotfiles/.zshrc ~/.zshrc
ln -s .dotfiles/.zshenv ~/.zshenv
ln -s .dotfiles/.zshlogin ~/.zshlogin
ln -s .dotfiles/.zimrc ~/.zimrc
ln -s .dotfiles/.gemrc ~/.gemrc
ln -s .dotfiles/.gitconfig ~/.gitconfig
ln -s .dotfiles/.tool-versions ~/.tool-versions
popd

git submodule init
git submodule update
set -euo pipefail

DOTDIR="${HOME}/.dotfiles"
FILES=(.vimrc .zshrc .zshenv .zlogin .zimrc .gemrc .gitconfig .tool-versions)

for file in "${FILES[@]}"; do
target="${HOME}/${file}"
src="${DOTDIR}/${file}"
if [ -e "$target" ] && [ ! -L "$target" ]; then
mv "$target" "${target}.bak.$(date +%s)"
fi
ln -sfn "$src" "$target"
done

git submodule update --init --recursive

# XDG config symlinks where supported
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-${HOME}/.config}"
mkdir -p "${XDG_CONFIG_HOME}/vim" "${XDG_CONFIG_HOME}/git"

# vimrc -> ~/.config/vim/vimrc
vim_target="${XDG_CONFIG_HOME}/vim/vimrc"
vim_src="${DOTDIR}/.vimrc"
if [ -e "$vim_target" ] && [ ! -L "$vim_target" ]; then
mv "$vim_target" "${vim_target}.bak.$(date +%s)"
fi
ln -sfn "$vim_src" "$vim_target"

# git config -> ~/.config/git/config
git_target="${XDG_CONFIG_HOME}/git/config"
git_src="${DOTDIR}/.gitconfig"
if [ -e "$git_target" ] && [ ! -L "$git_target" ]; then
mv "$git_target" "${git_target}.bak.$(date +%s)"
fi
ln -sfn "$git_src" "$git_target"
150 changes: 129 additions & 21 deletions osx/setup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@

$notes = []

# Keep sudo alive for the duration of the setup
begin
if system('command -v sudo >/dev/null 2>&1')
system 'sudo -v'
Thread.new do
loop do
system 'sudo -n true'
sleep 60
end
end
end
rescue
# ignore
end

def step(name)
if $completed.include? name
puts "Step '#{name}' already done; skipping."
Expand Down Expand Up @@ -47,13 +62,13 @@ def clone(repo, destination)

def pip(packages, opts={})
packages = [packages].flatten
command = "pip3 install #{packages.join(" ")}"
command = "python3 -m pip install #{packages.join(" ")}"
system command
end

def gem(packages, opts={})
packages = [packages].flatten
command = "`asdf which gem` install #{packages.join(" ")}"
command = "asdf exec gem install #{packages.join(" ")}"
system command
end

Expand All @@ -64,30 +79,67 @@ def prompt(message)
end

step "Install homebrew" do
ENV["NONINTERACTIVE"] = "1"
command "/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
end

step "Prompt for ssh keys" do
prompt "Ensure your ssh keys are set up."
File.exists? File.expand_path("~/.ssh")
step "Check ssh keys" do
ssh_dir = File.expand_path("~/.ssh")
have_dir = File.exist?(ssh_dir)
have_key = ["id_ed25519.pub", "id_rsa.pub"].any? { |k| File.exist?(File.join(ssh_dir, k)) }
unless have_dir && have_key
note "SSH keys not found. Generate one with: ssh-keygen -t ed25519 -C \"your_email@example.com\""
end
true
end

step "Install dotfiles" do
clone "git@github.com:arjun810/dotfiles", "~/.dotfiles"
dest = File.expand_path("~/.dotfiles")
if File.exist?(dest)
puts "~/.dotfiles already exists; skipping clone."
true
else
clone "git@github.com:arjun810/dotfiles", dest
end
end

step "Install Homebrew bundle" do
prefix = `brew --prefix 2>/dev/null`.strip
if prefix.nil? || prefix.empty?
prefix = File.directory?("/opt/homebrew") ? "/opt/homebrew" : "/usr/local"
end
ENV["PATH"] = "#{prefix}/bin:#{ENV["PATH"]}"
command "brew bundle --file ~/.dotfiles/osx/Brewfile"
end

step "Install ruby build dependencies" do
prefix = `brew --prefix 2>/dev/null`.strip
if prefix.nil? || prefix.empty?
prefix = File.directory?("/opt/homebrew") ? "/opt/homebrew" : "/usr/local"
end
ENV["PATH"] = "#{prefix}/bin:#{ENV["PATH"]}"
deps = %w[autoconf bison openssl@3 readline libyaml gmp zlib]
command "brew install #{deps.join(' ')}"
end

step "Install ruby" do
command "asdf plugin add ruby"
command "asdf install ruby latest"
plugins = `asdf plugin list 2>/dev/null`.lines.map { |l| l.strip }
added = true
unless plugins.include?("ruby")
added = command "asdf plugin add ruby"
end
installed = command "asdf install ruby latest"
set_global = command "asdf global ruby latest"
added && installed && set_global
end

step "Prompt for vim-plug" do
prompt "Ensure you've installed vim-plug."
File.exists? File.expand_path("~/.vim/autoload/plug.vim")
step "Install vim-plug" do
plug_path = File.expand_path("~/.vim/autoload/plug.vim")
if File.exist?(plug_path)
true
else
command 'curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'
end
end

step "Init dotfiles" do
Expand All @@ -99,39 +151,93 @@ def prompt(message)
end

step "Add zsh to system shells" do
command "echo /opt/homebrew/bin/zsh | sudo tee -a /etc/shells"
shells_file = "/etc/shells"
prefix = `brew --prefix 2>/dev/null`.strip
if prefix.nil? || prefix.empty?
prefix = File.directory?("/opt/homebrew") ? "/opt/homebrew" : "/usr/local"
end
zsh_path = File.join(prefix, "bin", "zsh")
lines = File.readlines(shells_file).map { |l| l.strip }
if lines.include?(zsh_path)
true
else
command "echo #{zsh_path} | sudo tee -a #{shells_file}"
end
end

step "Change shell to zsh" do
command "chsh -s /opt/homebrew/bin/zsh"
current = ENV["SHELL"]
prefix = `brew --prefix 2>/dev/null`.strip
if prefix.nil? || prefix.empty?
prefix = File.directory?("/opt/homebrew") ? "/opt/homebrew" : "/usr/local"
end
target = File.join(prefix, "bin", "zsh")
if current == target
true
else
command "chsh -s #{target}"
end
end

step "Install Monaco" do
note "You should remember to change your terminal font to Monaco for Powerline."
prompt "A prompt will pop up so you can install Monaco. Make sure to hit 'Install Font.'"
note "Change your terminal font to Monaco for Powerline (installed in ~/.dotfiles/osx)."
command 'open ~/.dotfiles/osx/"Monaco for Powerline.otf"'
end

step "Install iTerm2 colorschemes and fonts." do
prompt "Don't forget to add the iTerm colorschemes. They're in ~/.dotfiles/osx."
prompt "Don't forget to set up iTerm's fonts and update it so Powerline works."
note "Import iTerm2 color schemes from ~/.dotfiles/osx and set Powerline-compatible fonts."
true
end

step "Install zim" do
command "mkdir ~/.zim"
command "wget https://github.com/zimfw/zimfw/releases/latest/download/zimfw.zsh -O ~/.zim/zimfw.zsh"
ok = command "mkdir -p ~/.zim"
ok = ok && command "wget https://github.com/zimfw/zimfw/releases/latest/download/zimfw.zsh -O ~/.zim/zimfw.zsh"
ok = ok && command "zsh -i -c \"~/.zim/zimfw.zsh install\""
ok
end

step "Install bundler" do
gem "bundler"
end

step "Install hex" do
command "mix local.hex"
if system('mix --version >/dev/null 2>&1')
command "mix local.hex --force"
else
note "Elixir/mix not found; install Elixir/Erlang via Homebrew or asdf before running mix commands."
true
end
end

step "Install phoenix application generator" do
command "mix archive.install hex phx_new"
if system('mix --version >/dev/null 2>&1')
command "mix archive.install hex phx_new --force"
else
note "Skipping Phoenix installer because mix is not available."
true
end
end

step "Install pipx" do
prefix = `brew --prefix 2>/dev/null`.strip
if prefix.nil? || prefix.empty?
prefix = File.directory?("/opt/homebrew") ? "/opt/homebrew" : "/usr/local"
end
ENV["PATH"] = "#{prefix}/bin:#{ENV["PATH"]}"
ok = command "brew install pipx"
ok = ok && command "pipx ensurepath"
ok
end

step "Install Nerd Font via Homebrew cask" do
prefix = `brew --prefix 2>/dev/null`.strip
if prefix.nil? || prefix.empty?
prefix = File.directory?("/opt/homebrew") ? "/opt/homebrew" : "/usr/local"
end
ENV["PATH"] = "#{prefix}/bin:#{ENV["PATH"]}"
ok = command "brew tap homebrew/cask-fonts"
ok = ok && command "brew install --cask font-meslo-lg-nerd-font"
ok
end

# .amethyst has to be done manually since it's osx specific
Expand All @@ -148,3 +254,5 @@ def prompt(message)
$notes.each do |note|
puts note
end

puts "Setup complete. Review the notes above for any manual actions."