So today we are going to learn how to automate our dev environment with ansible.
Bootstrapping
Similar to my dotfiles script, we need to install necessary dependencies before running the playbooks. For that we need these dependencies:
xcode
brew
git, python
Putting into code:
bootstrap(){
info "Bootstraping..."
info "Installing xcode"
install_xcode
info "Installing HomeBrew"
install_homebrew
info "Installing python3"
install_brew_formula "python3"
info "Installing git"
install_brew_formula "git"PATH="/usr/local/bin:$(/opt/homebrew/bin/python3 -m site --user-base)/bin:$PATH"export PATH
info "Installing pip"
curl https://bootstrap.pypa.io/get-pip.py | python3
}
Installing ansible
I'm using virtualenv to fetch all ansible dependencies, without installing them globally.
python3 -m pip install--user virtualenv
virtualenv venv
. venv/bin/activate
info "Installing Ansible"
pip install ansible
info "Setting up Ansible"
ansible-galaxy collection install-r setup/requirements.yml
Requirements are:
-name:community.crypto-name:community.general
The collection community.crypto will be used to generate SSH keys.
At this point we have all our requirements. Let's write the playbook.
autoenv playbook
To replicate my .dotfiles commands, I have set up the following tasks:
[+] Installing all the apps, brew packages and casks
For this we can use the community.general. We can define some list variables with all the apps/packages/apps we use. Then we execute as follows:
Same for taps and casks.
For App store apps we use mas and ansible.builtin.command to run a shell command in loop:
-name:Install app store appsansible.builtin.command:"masinstall{{item}}"loop:"{{homebrew['mas']}}"tags:[packages,mac_app_store]
[+] Writing all my macOS settings
Here we will need community.general.osx_defaults. We need to define a list with settings, having domain, key, value and type for each setting. E.g.
-domain:com.apple.TimeMachinekey:DoNotOfferNewDisksForBackupname:Disable prompting to use new exteral drives as Time Machine volumetype:boolvalue:'true'
After defining all our settings, this task is defined as follows:
I've seen many setups with ansible using the file copy functionality to manag dotfiles, but this is not suitable for me. I prefer using stow so any change on the symlinks can be pushed to the git repo.
I opted for using the same bash script from my dotfiles repo and call the script as a command:
[+] Setting vs code as default for all the source code extensions
For this I just needed to set a list variable with all the extension and then loop into a shell command:
-name:Set VSCode as default editoransible.builtin.shell:|local exts=("{{ fileExtensions | join(' ') }}")for ext in $exts; doduti -s com.microsoft.VSCode $ext alldoneexit 0tags:settings
[+] Installing vim-plug
Same as before, another shell command will do the trick.
But here I also wanted to check if vim-plug was already installed. And for that ansible provides a file stat api and conditional tasks using the when keyword.
Note:ssh_passphrase and ssh_key_types_to_generate are variables.
[+] Uploading keys to github
In this task I want to upload my ssh key to github. For that I need a github token and use the community.general collection:
-name:Register SSH key with Githubvars:github_keys:-"{{ansible_user_dir}}/.ssh/id_ed25519.pub"pubkey:"{{lookup('first_found',github_keys,errors='ignore')}}"community.general.github_key:name:"{{ansible_user_id}}@{{ansible_hostname}}"pubkey:"{{lookup('file',pubkey)}}"state:presenttoken:"{{github['personal_token']}}"tags:github
[+] Installing pip packages
To install our pip packages we can use the ansible.buitin.pip command and declare a list variable with the packages to install:
So I had a few things left off that weren't suitable to run with ansible since they need some interaction: running rustup-init, installing nvim plugins and restarting the system. Basically:
nvim +PlugInstall +qall
if!hash rustc &>/dev/null;then
info "Triggering Rust-up"
rustup-init
fi
info "Done"
info "System must restart. Restart?"select yn in"y""n";do
case$ynin
y )sudo shutdown -r now;break;;
n )exit;;esacdone
We are completely done 🤖
Conclusions
I decided to try ansible out of curiosity to manage my dev environment. It is a very robust and versatile solution but it feels like an overkill for this task. I definitely like to set configuration files in yaml but my configurations/variables inside bash files are not too bloated to justify the switch (though I wrote the whole thing for ansible already). For now I'd prefer to continue using my bash+stow solution.
From there, ansible takes over with the autoenv playbook.
When ansible is done, the post-install script run commands that are not suitable for ansible.
Github Token
ansible will upload the ssh key to github, for that you need to export a GITHUB_TOKEN before running the scripts.
Customization
Most of the customizable configs reside on the grou-vars definitions.
You can check all the system settings, brew packages/casks and app store apps that will be installed.