diff --git a/dev/README.md b/dev/README.md
new file mode 100644
index 0000000..f261fa6
--- /dev/null
+++ b/dev/README.md
@@ -0,0 +1,9 @@
+```sh
+# Run bootstrap
+./bootstrap.sh
+
+# Configure 1Password
+
+# Run Ansible
+ansible-playbook main.yml
+```
diff --git a/dev/ansible.cfg b/dev/ansible.cfg
new file mode 100644
index 0000000..b723a25
--- /dev/null
+++ b/dev/ansible.cfg
@@ -0,0 +1,5 @@
+[defaults]
+inventory = hosts.yml
+
+# [privilege_escalation]
+# become_ask_pass = True
diff --git a/dev/bootstrap.sh b/dev/bootstrap.sh
new file mode 100755
index 0000000..2ad286a
--- /dev/null
+++ b/dev/bootstrap.sh
@@ -0,0 +1,5 @@
+# install homebrew
+/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+
+export PATH=/opt/homebrew/bin:$PATH
+brew install ansible 1password 1password/tap/1password-cli
diff --git a/dev/defaults.yml b/dev/defaults.yml
new file mode 100644
index 0000000..178e899
--- /dev/null
+++ b/dev/defaults.yml
@@ -0,0 +1,255 @@
+- hosts: all
+ tasks:
+
+ - name: set OS X defaults
+ osx_defaults:
+ domain: "{{ item.domain | default(omit) }}"
+ key: "{{ item.key }}"
+ type: "{{ item.type }}"
+ value: "{{ item.value }}"
+ notify: restart OS X system services
+ with_items:
+
+ # NSGlobalDomain defaults
+ - # don't quit idle applications
+ domain: -g
+ key: NSDisableAutomaticTermination
+ type: bool
+ value: true
+ - # disable font smoothing
+ key: AppleFontSmoothing
+ type: int
+ value: 0
+ - # full keyboard access
+ key: AppleKeyboardUIMode
+ type: int
+ value: 3
+ - # show all extensions by default
+ key: AppleShowAllExtensions
+ type: bool
+ value: true
+ - # keyboard repeat rate
+ key: KeyRepeat
+ type: int
+ value: 2
+ - # delay before keyboard repeat
+ key: InitialKeyRepeat
+ type: int
+ value: 25
+ - # set sidebar item size to small
+ key: NSTableViewDefaultSizeMode
+ type: int
+ value: 1
+ - # disable resume
+ key: NSQuitAlwaysKeepsWindows
+ type: bool
+ value: false
+ - # add debug menu in web views
+ key: WebKitDeveloperExtras
+ type: bool
+ value: true
+ - # tap to click
+ key: com.apple.mouse.tapBehavior
+ type: bool
+ value: true
+ - # only show scrollbars when scrolling
+ key: AppleShowScrollBars
+ type: string
+ value: WhenScrolling
+
+ # Safari
+ - # enable Debug menu in Safari
+ domain: com.apple.Safari
+ key: IncludeInternalDebugMenu
+ type: bool
+ value: true
+ - # disable Java
+ domain: com.apple.Safari
+ key: com.apple.Safari.ContentPageGroupIdentifier.WebKit2JavaEnabled
+ type: bool
+ value: false
+ - # disable Java
+ domain: com.apple.Safari
+ key: com.apple.Safari.ContentPageGroupIdentifier.WebKit2JavaEnabledForLocalFiles
+ type: bool
+ value: false
+
+ - # disable dashboard
+ domain: com.apple.dashboard
+ key: mcx-disabled
+ type: bool
+ value: true
+
+ - # don't write .DS_Store to network volumes
+ domain: com.apple.desktopservices
+ key: DSDontWriteNetworkStores
+ type: bool
+ value: true
+
+ # Dock defaults
+ - # automatically hide and show the dock
+ domain: com.apple.dock
+ key: autohide
+ type: bool
+ value: true
+ - # minimize windows using the scale effect
+ domain: com.apple.dock
+ key: mineffect
+ type: string
+ value: scale
+ - # don't rearrange spaces
+ domain: com.apple.dock
+ key: mru-spaces
+ type: bool
+ value: false
+ - domain: com.apple.dock
+ key: orientation
+ type: string
+ value: left
+ - # set the bottom left hot corner to sleep the display
+ domain: com.apple.dock
+ key: wvous-bl-corner
+ type: int
+ value: 10
+ - # set the icon size to 36 pixels
+ domain: com.apple.dock
+ key: tilesize
+ type: int
+ value: 36
+ - # no dock delay
+ domain: com.apple.dock
+ key: autohide-delay
+ type: float
+ value: 0
+
+ # Trackpad settings
+ - domain: com.apple.driver.AppleBluetoothMultitouch.trackpad
+ key: Clicking
+ type: int
+ value: 1
+ - domain: com.apple.driver.AppleBluetoothMultitouch.trackpad
+ key: TrackpadFourFingerVertSwipeGesture
+ type: int
+ value: 0
+ - domain: com.apple.driver.AppleBluetoothMultitouch.trackpad
+ key: TrackpadThreeFingerDrag
+ type: bool
+ value: true
+ - domain: com.apple.driver.AppleBluetoothMultitouch.trackpad
+ key: TrackpadThreeFingerHorizSwipeGesture
+ type: int
+ value: 0
+ - domain: com.apple.driver.AppleBluetoothMultitouch.trackpad
+ key: TrackpadThreeFingerVertSwipeGesture
+ type: int
+ value: 0
+
+ # Finder defaults
+ - # don't ask when changing file extension
+ domain: com.apple.finder
+ key: FXEnableExtensionChangeWarning
+ type: bool
+ value: false
+ - # default to list view
+ domain: com.apple.finder
+ key: FXPreferredViewStyle
+ type: string
+ value: Nlsv
+ - # enable text selection in QuickLook
+ domain: com.apple.finder
+ key: QLEnableTextSelection
+ type: bool
+ value: true
+ - # show full path in Finder
+ domain: com.apple.finder
+ key: _FXShowPosixPathInTitle
+ type: bool
+ value: true
+ - # remove the proxy icon hover delay
+ domain: com.apple.Finder
+ key: NSToolbarTitleViewRolloverDelay
+ type: float
+ value: 0
+ - # show the proxy icon and older titlebar
+ # https://twitter.com/chucker/status/1395843084383043584
+ domain: com.apple.Finder
+ key: NSWindowSupportsAutomaticInlineTitle
+ type: bool
+ value: false
+
+ - # set date format in menubar
+ domain: com.apple.menuextra.clock
+ key: DateFormat
+ type: string
+ value: h:mm
+
+ - # no window shadows when capturing windows
+ domain: com.apple.screencapture
+ key: disable-shadow
+ type: bool
+ value: true
+
+ - domain: com.apple.screencapture
+ key: location
+ type: string
+ value: "{{ ansible_env.HOME }}/Downloads"
+
+ - domain: com.apple.screensaver
+ key: askForPassword
+ type: int
+ value: 1
+
+ - domain: com.apple.Terminal
+ key: ShowLineMarks
+ type: bool
+ value: false
+
+ # Other applications
+ - domain: com.google.Chrome
+ key: AppleEnableSwipeNavigateWithScrolls
+ type: bool
+ value: false
+
+ - domain: org.vim.MacVim
+ key: MMLastWindowClosedBehavior
+ type: int
+ value: 2
+
+ - domain: org.vim.MacVim
+ key: MMUntitledWindow
+ type: int
+ value: 1
+
+ - domain: org.vim.MacVim
+ key: SUCheckAtStartup
+ type: int
+ value: 1
+
+ - domain: org.vim.MacVim
+ key: SUEnableAutomaticChecks
+ type: int
+ value: 1
+
+ - domain: com.freron.MailMate
+ key: MmAllowedImageURLRegexp
+ type: string
+ value: https://((i|images|d)\.gr-assets\.com|www\.goodreads\.com|massdrop-s3\.imgix\.net|.*\.cloudfront\.net|s3\.amazonaws\.com|files\.convertkitcdn\.com/assets/pictures)/.*
+
+ - domain: com.freron.MailMate
+ key: MmSendMessageDelayEnabled
+ type: bool
+ value: true
+
+ - domain: com.freron.MailMate
+ key: MmSendMessageDelay
+ type: int
+ value: 60
+
+ handlers:
+
+ - name: restart OS X system services
+ command: killall {{ item }}
+ with_items:
+ - Finder
+ - Dock
+ - SystemUIServer
diff --git a/dev/dock.yml b/dev/dock.yml
new file mode 100644
index 0000000..7d5d8a2
--- /dev/null
+++ b/dev/dock.yml
@@ -0,0 +1,23 @@
+- hosts: all
+ tasks:
+
+ # - name: install dockutil
+ # homebrew: name=dockutil state=present
+
+ - command: dockutil --list
+ register: dockutil_result
+ - name: remove all Dock icons
+ command: dockutil --remove all
+ # when: not (dockutil_result.stdout_lines | length == 1 and dockutil_result.stdout is search("^Downloads"))
+ - name: add ~/Downloads to the Dock
+ command: dockutil --add ~/Downloads
+ --view list
+ --display stack
+ --sort datemodified
+ when: not (dockutil_result.stdout_lines | length == 1 and dockutil_result.stdout is search("^Downloads"))
+
+ - name: remove Dockutil
+ homebrew: name=dockutil state=absent
+
+ - name: restart Dock
+ command: killall Dock
diff --git a/dev/dotfiles.yml b/dev/dotfiles.yml
new file mode 100644
index 0000000..fb78117
--- /dev/null
+++ b/dev/dotfiles.yml
@@ -0,0 +1,29 @@
+- hosts: all
+ tasks:
+
+ - name: Symlink dotfiles
+ file:
+ src: ~/.dotfiles/{{ item }}
+ dest: ~/{{ item }}
+ state: link
+ loop:
+ - .config
+ - .gitconfig
+ - .hammerspoon
+ - .local
+ - .ssh
+ - .tmux.conf
+ - .zsh
+
+ - name: Symlink Prezto
+ file:
+ src: ~/.dotfiles/.zprezto
+ dest: ~/.zprezto
+ state: link
+
+ - name: Symlink Prezto runcoms
+ file:
+ src: "{{ item }}"
+ dest: ~/.{{ item | basename }}
+ state: link
+ with_fileglob: "~/.zprezto/runcoms/z*"
diff --git a/dev/homebrew.yml b/dev/homebrew.yml
new file mode 100644
index 0000000..a3a41eb
--- /dev/null
+++ b/dev/homebrew.yml
@@ -0,0 +1,49 @@
+- hosts: all
+ tasks:
+
+ - community.general.homebrew:
+ name: "{{ item }}"
+ loop:
+ - chruby
+ - difftastic
+ - direnv
+ - efm-langserver
+ - entr
+ - exa
+ - fasd
+ - fd
+ - fzf
+ - git
+ - git-lfs
+ - jq
+ - luarocks
+ - neovim
+ - ripgrep
+ - ruby-install
+ - shellcheck
+ - svn # required for source code pro
+ - tmux
+ - tree
+ - zsh
+
+ - fabianishere/personal/pam_reattach
+
+ - community.general.homebrew_cask:
+ name: "{{ item }}"
+ loop:
+ - alfred
+ - bartender
+ - dash
+ - fantastical
+ - firefox
+ - google-chrome
+ - hammerspoon
+ - mailmate
+ - obsidian
+ - slack
+ - topnotch
+ - zoom
+
+ # - homebrew/cask-fonts/font-source-code-pro
+ - homebrew/cask-fonts/font-sauce-code-pro-nerd-font
+ - homebrew/cask-versions/firefox-developer-edition
diff --git a/dev/hosts.yml b/dev/hosts.yml
new file mode 100644
index 0000000..fe1528d
--- /dev/null
+++ b/dev/hosts.yml
@@ -0,0 +1,5 @@
+all:
+ hosts:
+ localhost:
+ ansible_connection: local
+ ansible_python_interpreter: "{{ansible_playbook_python}}"
diff --git a/dev/macos.yml b/dev/macos.yml
new file mode 100644
index 0000000..3bb27c4
--- /dev/null
+++ b/dev/macos.yml
@@ -0,0 +1,38 @@
+- hosts: all
+ tasks:
+
+ - file: path=~/Library/KeyBindings state=directory
+ - name: Symlink Emacs-style keybindings for OS X
+ file:
+ src: ~/.dotfiles/macos/DefaultKeyBinding.dict
+ dest: ~/Library/KeyBindings/DefaultKeyBinding.dict
+ state: link
+
+ - file: path=~/Library/Colors state=directory
+ - name: symlink OS X colors palettes
+ file: src={{ item }} dest=~/Library/Colors/{{ item | basename }} state=link
+ with_fileglob: ~/.dotfiles/macos/colors/*
+
+ - file: path=~/Library/Dictionaries state=directory
+ - name: Install Webster's 1913 dictionary
+ copy:
+ src: ~/.dotfiles/macos/websters-1913.dictionary/
+ dest: ~/Library/Dictionaries/websters-1913.dictionary
+
+ - name: Enable Touch ID for sudo
+ block:
+ - lineinfile:
+ path: /etc/pam.d/sudo
+ insertafter: '^auth\s+sufficient'
+ regexp: '^auth\s+sufficient\s+pam_tid.so$'
+ line: "auth\tsufficient\tpam_tid.so"
+
+ # tmux
+ - shell: brew --prefix
+ register: brew_prefix
+ - lineinfile:
+ path: /etc/pam.d/sudo
+ insertbefore: '^auth\tsufficient\tpam_tid.so'
+ regexp: '^auth\s+optional\s+.*pam_reattach.so$'
+ line: "auth\toptional\t{{ brew_prefix.stdout | trim }}/lib/pam/pam_reattach.so"
+ become: yes
diff --git a/dev/main.yml b/dev/main.yml
new file mode 100644
index 0000000..e048e3d
--- /dev/null
+++ b/dev/main.yml
@@ -0,0 +1,37 @@
+- hosts: all
+ tasks:
+
+ - group_by: key=os_{{ ansible_distribution }}
+ tags: always
+
+ - ansible.builtin.git:
+ repo: git@git.kejadlen.dev:alpha/dotfiles.git
+ dest: ~/.dotfiles.git
+ bare: true
+
+ # macOS things
+
+ - name: Import terminal profile
+ ansible.builtin.command: open ~/.macos/Alpha.terminal
+
+ - name: Set terminal profile to be the default
+ osx_defaults:
+ domain: com.apple.Terminal
+ key: "{{ item }} Window Settings"
+ type: string
+ value: Alpha
+ with_items:
+ - Default
+ - Startup
+
+- import_playbook: homebrew.yml
+
+- import_playbook: defaults.yml
+- import_playbook: dock.yml
+- import_playbook: macos.yml
+
+- hosts: all
+ tasks:
+
+ - ansible.builtin.command: "luarocks install fennel"
+