diff --git a/.github/ISSUE_TEMPLATE/setup.md b/.github/ISSUE_TEMPLATE/setup.md
index 59b757d..83664b1 100644
--- a/.github/ISSUE_TEMPLATE/setup.md
+++ b/.github/ISSUE_TEMPLATE/setup.md
@@ -30,11 +30,15 @@
ansible-playbook main.yml --ask-pass --ask-become-pass
```
-- [ ] Set Keyboard preferences
- - [ ] Modifiers
- - [ ] Function keys
+- [ ] macOS preferences
+ - [ ] Displays
+ - [ ] Enable Night Shift
+ - [ ] Keyboard
+ - [ ] Modifiers
+ - [ ] Function keys
- [ ] Setup programs
- [ ] 1Password
- [ ] Firefox
+ - [ ] Fantastical
- [ ] Arq
diff --git a/README.md b/README.md
index 2dada3d..6135ba6 100644
--- a/README.md
+++ b/README.md
@@ -16,19 +16,11 @@ provision new machines.
# Usage
-First, some steps need to be performed on the remote machine that I couldn't
-figure out how to automate:
+Use a [tracking issue][tracking-issue] as a checklist for local provisioning.
-- [ ] Enable Remote Login in System Preferences -> Sharing.
-- [ ] Install [Xcode][xcode]: `open 'https://itunes.apple.com/us/app/xcode/id497799835?mt=12'`
-- [ ] Accept the Xcode license: `sudo xcodebuild -license`
-- [ ] Open Xcode
-- [ ] Install the command line developer tools: `xcode-select --install`. (It looks like the
- Homebrew installer [_should_][xcode-select-cli] be able to handle this, but I haven't been able to
- get it to work headless.)
+[tracking-issue]: https://github.com/kejadlen/dotfiles/issues/new?template=setup.md
-[xcode]: https://itunes.apple.com/us/app/xcode/id497799835?mt=12
-[xcode-select-cli]: https://github.com/Homebrew/install/blob/master/install#L207-L216
+## Remote provisioning
On the control machine:
@@ -50,34 +42,6 @@ manually run post-provisioning:
# Symlink ~/.dotfiles to Dropbox
rm -rf ~/.dotfiles
ln -s ~/Dropbox/dotfiles ~/.dotfiles
-
-# Add private SSH keys
-ruby ~/.dotfiles/scripts/setup_ssh_keys.rb
-
-# Install apps from the Mac App Store
-open 'https://itunes.apple.com/us/app/reeder-3/id880001334?ls=1&mt=12'
-open 'https://itunes.apple.com/us/app/paprika-recipe-manager/id451907568?mt=12'
-```
-
-## Provisioning Locally
-
-You can also use this to provision a machine by itself, although this won't
-be able to copy over OS X application settings, for obvious reasons.
-
-``` shell
-# Install Homebrew
-ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
-
-# Install ansible
-brew install ansible
-
-# Clone dotfiles
-git clone --recursive https://github.com/kejadlen/dotfiles.git ~/.dotfiles
-
-# Run Ansible
-cd ~/.dotfiles/ansible
-echo "localhost ansible_connection=local" > hosts.private
-ansible-playbook main.yml --ask-pass --ask-become-pass
```
# Development
diff --git a/ansible/macbook_pro.yml b/ansible/macbook_pro.yml
deleted file mode 100644
index bfd8be1..0000000
--- a/ansible/macbook_pro.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-- hosts: all
- tasks:
- - name: register keyboard_id
- shell: ioreg -n IOHIDKeyboard -r | grep -e VendorID\" -e ProductID | ruby -e 'print ARGF.read.scan(/\d+/).join(?-)'
- register: keyboard_id
-
- - name: s/caps lock/ctrl/
- command: defaults -currentHost write -g com.apple.keyboard.modifiermapping.{{ keyboard_id.stdout }}-0 -array-add 'HIDKeyboardModifierMappingDst2HIDKeyboardModifierMappingSrc0'
-
- - name: install logitech options
- homebrew_cask: name=logitech-options state=installed
diff --git a/ansible/main.yml b/ansible/main.yml
index 24ef6d3..280fc6d 100644
--- a/ansible/main.yml
+++ b/ansible/main.yml
@@ -1,4 +1,3 @@
----
- hosts: all
tasks:
- group_by: key=os_{{ ansible_distribution }}
diff --git a/ansible/playbooks/homebrew.yml b/ansible/playbooks/homebrew.yml
index 3e6d25d..f79b59f 100644
--- a/ansible/playbooks/homebrew.yml
+++ b/ansible/playbooks/homebrew.yml
@@ -14,7 +14,7 @@
- homebrew/cask-versions
- homebrew/core
- homebrew/services
- - kejadlen/personal
+ - kejadlen/tap
- seattle-beach/tap
- name: install formulae
@@ -87,6 +87,7 @@
with_items:
- 1028916583 # iPulse
- 451907568 # Paprika Recipe Manager
+ - 497799835 # Xcode
- 880001334 # Reeder
- 904280696 # Things3
- 585829637 # Todoist
diff --git a/ansible/playbooks/rust.yml b/ansible/playbooks/rust.yml
new file mode 100644
index 0000000..fcc5ad2
--- /dev/null
+++ b/ansible/playbooks/rust.yml
@@ -0,0 +1,5 @@
+# - hosts: os_MacOSX
+- hosts: all
+ tasks:
+ - name: install rustup
+ shell: curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain nightly
diff --git a/ansible/roles/osx/library/my_defaults.py b/ansible/roles/osx/library/my_defaults.py
deleted file mode 100644
index 9ff179c..0000000
--- a/ansible/roles/osx/library/my_defaults.py
+++ /dev/null
@@ -1,354 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# (c) 2014, GeekChimp - Franck Nijhof
-#
-# Ansible is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Ansible is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Ansible. If not, see .
-
-DOCUMENTATION = '''
----
-module: osx_defaults
-author: Franck Nijhof (@frenck)
-short_description: osx_defaults allows users to read, write, and delete Mac OS X user defaults from Ansible
-description:
- - osx_defaults allows users to read, write, and delete Mac OS X user defaults from Ansible scripts.
- Mac OS X applications and other programs use the defaults system to record user preferences and other
- information that must be maintained when the applications aren't running (such as default font for new
- documents, or the position of an Info panel).
-version_added: "2.0"
-options:
- domain:
- description:
- - The domain is a domain name of the form com.companyname.appname.
- required: false
- default: NSGlobalDomain
- key:
- description:
- - The key of the user preference
- required: true
- type:
- description:
- - The type of value to write.
- required: false
- default: string
- choices: [ "array", "bool", "boolean", "date", "float", "int", "integer", "string" ]
- array_add:
- description:
- - Add new elements to the array for a key which has an array as its value.
- required: false
- default: false
- choices: [ "true", "false" ]
- value:
- description:
- - The value to write. Only required when state = present.
- required: false
- default: null
- state:
- description:
- - The state of the user defaults
- required: false
- default: present
- choices: [ "present", "absent" ]
-notes:
- - Apple Mac caches defaults. You may need to logout and login to apply the changes.
-'''
-
-EXAMPLES = '''
-- osx_defaults: domain=com.apple.Safari key=IncludeInternalDebugMenu type=bool value=true state=present
-- osx_defaults: domain=NSGlobalDomain key=AppleMeasurementUnits type=string value=Centimeters state=present
-- osx_defaults: key=AppleMeasurementUnits type=string value=Centimeters
-- osx_defaults:
- key: AppleLanguages
- type: array
- value: ["en", "nl"]
-- osx_defaults: domain=com.geekchimp.macable key=ExampleKeyToRemove state=absent
-'''
-
-from datetime import datetime
-
-# exceptions --------------------------------------------------------------- {{{
-class OSXDefaultsException(Exception):
- pass
-# /exceptions -------------------------------------------------------------- }}}
-
-# class MacDefaults -------------------------------------------------------- {{{
-class OSXDefaults(object):
-
- """ Class to manage Mac OS user defaults """
-
- # init ---------------------------------------------------------------- {{{
- """ Initialize this module. Finds 'defaults' executable and preps the parameters """
- def __init__(self, **kwargs):
-
- # Initial var for storing current defaults value
- self.current_value = None
-
- # Just set all given parameters
- for key, val in kwargs.iteritems():
- setattr(self, key, val)
-
- # Try to find the defaults executable
- self.executable = self.module.get_bin_path(
- 'defaults',
- required=False,
- opt_dirs=self.path.split(':'),
- )
-
- if not self.executable:
- raise OSXDefaultsException("Unable to locate defaults executable.")
-
- # When state is present, we require a parameter
- if self.state == "present" and self.value is None:
- raise OSXDefaultsException("Missing value parameter")
-
- # Ensure the value is the correct type
- self.value = self._convert_type(self.type, self.value)
-
- # /init --------------------------------------------------------------- }}}
-
- # tools --------------------------------------------------------------- {{{
- """ Converts value to given type """
- def _convert_type(self, type, value):
-
- if type == "string":
- return str(value)
- elif type in ["bool", "boolean"]:
- if isinstance(value, basestring):
- value = value.lower()
- if value in [True, 1, "true", "1", "yes"]:
- return True
- elif value in [False, 0, "false", "0", "no"]:
- return False
- raise OSXDefaultsException("Invalid boolean value: {0}".format(repr(value)))
- elif type == "date":
- try:
- return datetime.strptime(value.split("+")[0].strip(), "%Y-%m-%d %H:%M:%S")
- except ValueError:
- raise OSXDefaultsException(
- "Invalid date value: {0}. Required format yyy-mm-dd hh:mm:ss.".format(repr(value))
- )
- elif type in ["int", "integer"]:
- if not str(value).isdigit():
- raise OSXDefaultsException("Invalid integer value: {0}".format(repr(value)))
- return int(value)
- elif type == "float":
- try:
- value = float(value)
- except ValueError:
- raise OSXDefaultsException("Invalid float value: {0}".format(repr(value)))
- return value
- elif type == "array":
- if not isinstance(value, list):
- raise OSXDefaultsException("Invalid value. Expected value to be an array")
- return value
-
- raise OSXDefaultsException('Type is not supported: {0}'.format(type))
-
- """ Converts array output from defaults to an list """
- @staticmethod
- def _convert_defaults_str_to_list(value):
-
- # Split output of defaults. Every line contains a value
- value = value.splitlines()
-
- # Remove first and last item, those are not actual values
- value.pop(0)
- value.pop(-1)
-
- # Remove extra spaces and comma (,) at the end of values
- value = [re.sub(',$', '', x.strip(' ')) for x in value]
-
- return value
- # /tools -------------------------------------------------------------- }}}
-
- # commands ------------------------------------------------------------ {{{
- """ Reads value of this domain & key from defaults """
- def read(self):
- # First try to find out the type
- rc, out, err = self.module.run_command([self.executable, "read-type", self.domain, self.key])
-
- # If RC is 1, the key does not exists
- if rc == 1:
- return None
-
- # If the RC is not 0, then terrible happened! Ooooh nooo!
- if rc != 0:
- raise OSXDefaultsException("An error occurred while reading key type from defaults: " + out)
-
- # Ok, lets parse the type from output
- type = out.strip().replace('Type is ', '')
-
- # Now get the current value
- rc, out, err = self.module.run_command([self.executable, "read", self.domain, self.key])
-
- # Strip output
- out = out.strip()
-
- # An non zero RC at this point is kinda strange...
- if rc != 0:
- raise OSXDefaultsException("An error occurred while reading key value from defaults: " + out)
-
- # Convert string to list when type is array
- if type == "array":
- out = self._convert_defaults_str_to_list(out)
-
- # Store the current_value
- self.current_value = self._convert_type(type, out)
-
- """ Writes value to this domain & key to defaults """
- def write(self):
-
- # We need to convert some values so the defaults commandline understands it
- if type(self.value) is bool:
- if self.value:
- value = "TRUE"
- else:
- value = "FALSE"
- elif type(self.value) is int or type(self.value) is float:
- value = str(self.value)
- elif self.array_add and self.current_value is not None:
- value = list(set(self.value) - set(self.current_value))
- # elif isinstance(self.value, datetime):
- # value = self.value.strftime('%Y-%m-%d %H:%M:%S')
- else:
- value = self.value
-
- # When the type is array and array_add is enabled, morph the type :)
- if self.type == "array" and self.array_add:
- self.type = "array-add"
-
- # All values should be a list, for easy passing it to the command
- if not isinstance(value, list):
- value = [value]
-
- rc, out, err = self.module.run_command([self.executable, 'write', self.domain, self.key, '-' + self.type] + value)
-
- if rc != 0:
- raise OSXDefaultsException('An error occurred while writing value to defaults: ' + out)
-
- """ Deletes defaults key from domain """
- def delete(self):
- rc, out, err = self.module.run_command([self.executable, 'delete', self.domain, self.key])
- if rc != 0:
- raise OSXDefaultsException("An error occurred while deleting key from defaults: " + out)
-
- # /commands ----------------------------------------------------------- }}}
-
- # run ----------------------------------------------------------------- {{{
- """ Does the magic! :) """
- def run(self):
-
- # Get the current value from defaults
- self.read()
-
- # Handle absent state
- if self.state == "absent":
- print "Absent state detected!"
- if self.current_value is None:
- return False
- self.delete()
- return True
-
- # There is a type mismatch! Given type does not match the type in defaults
- if self.current_value is not None and type(self.current_value) is not type(self.value):
- raise OSXDefaultsException("Type mismatch. Type in defaults: " + type(self.current_value).__name__)
-
- # Current value matches the given value. Nothing need to be done. Arrays need extra care
- if self.type == "array" and self.current_value is not None and not self.array_add and \
- set(self.current_value) == set(self.value):
- return False
- elif self.type == "array" and self.current_value is not None and self.array_add and \
- len(list(set(self.value) - set(self.current_value))) == 0:
- return False
- elif self.current_value == self.value:
- return False
-
- # Change/Create/Set given key/value for domain in defaults
- self.write()
- return True
-
- # /run ---------------------------------------------------------------- }}}
-
-# /class MacDefaults ------------------------------------------------------ }}}
-
-
-# main -------------------------------------------------------------------- {{{
-def main():
- module = AnsibleModule(
- argument_spec=dict(
- domain=dict(
- default="NSGlobalDomain",
- required=False,
- ),
- key=dict(
- default=None,
- ),
- type=dict(
- default="string",
- required=False,
- choices=[
- "array",
- "bool",
- "boolean",
- "date",
- "float",
- "int",
- "integer",
- "string",
- ],
- ),
- array_add=dict(
- default=False,
- required=False,
- type='bool',
- ),
- value=dict(
- default=None,
- required=False,
- ),
- state=dict(
- default="present",
- required=False,
- choices=[
- "absent", "present"
- ],
- ),
- path=dict(
- default="/usr/bin:/usr/local/bin",
- required=False,
- )
- ),
- supports_check_mode=True,
- )
-
- domain = module.params['domain']
- key = module.params['key']
- type = module.params['type']
- array_add = module.params['array_add']
- value = module.params['value']
- state = module.params['state']
- path = module.params['path']
-
- try:
- defaults = OSXDefaults(module=module, domain=domain, key=key, type=type,
- array_add=array_add, value=value, state=state, path=path)
- changed = defaults.run()
- module.exit_json(changed=changed)
- except OSXDefaultsException, e:
- module.fail_json(msg=e.message)
-
-# /main ------------------------------------------------------------------- }}}
-
-from ansible.module_utils.basic import *
-main()