I have already started blogging about Ansible; in particular, I have shown how to develop and test an Ansible role with Molecule and Docker, also on Gitpod.
This blog post will describe my Ansible role for installing “Oh My Zsh” and several command line programs. The role also installs the starship prompt or the p10k theme. As for the other roles I’ve blogged about, this one is tested with Molecule and Docker and can be developed with Gitpod (see the linked posts above). In particular, it is tested in Arch, Ubuntu, and Fedora.
This role is for my personal installation and configuration and is not meant to be reusable.
The role can be found here: https://github.com/LorenzoBettini/ansible-molecule-oh-my-zsh-example.
My other post has already described many parts related to zsh installation, configuration, and verification with Ansible and Molecule.
The main file “tasks/main.yml” is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
--- # tasks file for oh_my_zsh - name: Install Git become: true ansible.builtin.package: state: present name: git - name: Install ZSH become: true ansible.builtin.package: name: zsh state: present - name: Override powerline fonts package name for Debian. ansible.builtin.set_fact: powerlinefonts: fonts-powerline when: ansible_os_family == 'Debian' - name: Override fd-find package name for Archlinux. ansible.builtin.set_fact: fdfind: fd when: ansible_os_family == 'Archlinux' - name: Install Powerline fonts become: true ansible.builtin.package: state: present name: "{{ powerlinefonts }}" - name: Install ripgrep become: true ansible.builtin.package: state: present name: ripgrep - name: Install procs in Arch/Fedora become: true ansible.builtin.package: state: present name: procs when: ansible_os_family != 'Debian' # unzip is useful for extracting zips - name: Install unzip become: true ansible.builtin.package: state: present name: unzip - name: Install procs in Ubuntu become: true ansible.builtin.unarchive: src: "https://github.com/dalance/procs/releases/download/{{ procsversion }}/procs-{{ procsversion }}-x86_64-linux.zip" dest: /usr/local/bin remote_src: true when: ansible_os_family == 'Debian' - name: Install dust on Arch become: true ansible.builtin.package: state: present name: dust when: ansible_os_family == 'Archlinux' # In Ubuntu/Fedora we have to install it from archive # so it's better to check if it's already installed - name: Check if dust is already installed on Ubuntu/Fedora ansible.builtin.shell: > dust --version register: dust_rc failed_when: false changed_when: false when: ansible_os_family != 'Archlinux' - name: Install dust on Ubuntu/Fedora become: true ansible.builtin.unarchive: src: "https://github.com/bootandy/dust/releases/download/{{ dustversion }}/dust-{{ dustversion }}-x86_64-unknown-linux-gnu.tar.gz" dest: /usr/local/bin extra_opts: - --strip=1 - --wildcards - '*/dust' remote_src: true when: ansible_os_family != 'Archlinux' and dust_rc.rc != 0 - name: Install Google Noto emoji fonts become: true ansible.builtin.package: state: present name: noto-fonts-emoji when: ansible_os_family == 'Archlinux' # To get "icons" in exa output - name: Install Patched font Arimo from nerd-fonts (Arch) become: true ansible.builtin.package: state: present name: ttf-arimo-nerd when: ansible_os_family == 'Archlinux' - name: Install exa become: true ansible.builtin.package: state: present name: exa - name: Install fd-find become: true ansible.builtin.package: state: present name: "{{ fdfind }}" - name: Create link fd to fdfind in Debian become: true ansible.builtin.file: src: '/usr/bin/fdfind' dest: '/usr/bin/fd' state: link when: ansible_os_family == 'Debian' - name: Install bat become: true ansible.builtin.package: state: present name: bat - name: Create link bat to batcat in Debian become: true ansible.builtin.file: src: '/usr/bin/batcat' dest: '/usr/bin/bat' state: link when: ansible_os_family == 'Debian' and ansible_distribution_version is version('23.04', '<') - name: Install zoxide become: true ansible.builtin.package: state: present name: zoxide # required by the OMZ plugin zsh-interactive-cd - name: Install fzf (command-line fuzzy finder) become: true ansible.builtin.package: state: present name: fzf - name: Install Oh My Zsh # noqa: latest ansible.builtin.git: repo: https://github.com/ohmyzsh/ohmyzsh.git dest: ~/.oh-my-zsh depth: 1 - name: Install zsh-autosuggestions plugin # noqa: latest ansible.builtin.git: repo: https://github.com/zsh-users/zsh-autosuggestions dest: ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions depth: 1 - name: Install zsh-completions plugin # noqa: latest ansible.builtin.git: repo: https://github.com/zsh-users/zsh-completions dest: ~/.oh-my-zsh/custom/plugins/zsh-completions depth: 1 - name: Install zsh-syntax-highlighting plugin # noqa: latest ansible.builtin.git: repo: https://github.com/zsh-users/zsh-syntax-highlighting.git dest: ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting depth: 1 - name: Change user shell to zsh become: true ansible.builtin.user: name: "{{ ansible_user_id }}" shell: /bin/zsh - name: Tasks for starship ansible.builtin.include_tasks: starship.yml when: with_starship - name: Tasks for p10k ansible.builtin.include_tasks: p10k.yml when: not with_starship |
Besides “zsh” and “git” (which are needed for installing other things, and, in general, I need it daily), this installs several command line tools, like ripgrep, procs, dust, exa, bat, zoxide. Note that, depending on the operating system, these tools must be installed differently (e.g., from the package manager or by downloading a binary distribution). In a few cases, the package names differ depending on the operating system. In such cases, the default names are defined in “vars/main.yml” and properly overridden depending on the operating system:
1 2 3 4 5 6 |
# vars file for oh_my_zsh powerlinefonts: powerline-fonts fdfind: fd-find dustversion: v0.8.3 procsversion: v0.13.3 with_starship: true |
The task also installs a few fonts (nerd fonts, with emoji, or with icon characters), which are needed because “starship” and “p10k” need a few icon characters; the same holds for other tools like exa.
“Oh My Zsh” is installed by cloning its Git repository; the same holds for some external plugins. The task also sets “zsh” as the default shell for the current user.
Finally, depending on the variable with_starship
, which defaults to true, it installs the starship prompt or the p10k theme. These are handled in the corresponding included files “starship.yml” and “p10k.yml”, respectively.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
--- # tasks file for starship - name: Install starship (cross-shell prompt) as a package become: true ansible.builtin.package: state: present name: starship when: ansible_os_family == 'Archlinux' - name: Install curl (for starship installation) become: true ansible.builtin.package: state: present name: curl when: ansible_os_family != 'Archlinux' - name: Get starship install script ansible.builtin.get_url: url: https://starship.rs/install.sh dest: /tmp/starship_install.sh mode: '0755' register: starship_installation_script when: ansible_os_family != 'Archlinux' - name: Install starship with installation script become: true ansible.builtin.shell: cmd: /tmp/starship_install.sh --yes executable: /bin/sh when: ansible_os_family != 'Archlinux' and starship_installation_script.changed # if the previous task hasn't changed, the shell script is already there # and we have already installed starship # we check this to satisfy idempotence - name: Copy zshrc (for starship) ansible.builtin.copy: src: zshrc-starship dest: ~/.zshrc mode: 0644 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
--- # tasks file for p10k - name: Install Powerline10k theme # noqa: latest ansible.builtin.git: repo: https://github.com/romkatv/powerlevel10k.git dest: ~/.oh-my-zsh/custom/themes/powerlevel10k depth: 1 - name: Copy zshrc (for p10k) ansible.builtin.copy: src: zshrc-p10k dest: ~/.zshrc mode: 0644 - name: Copy p10k.zsh ansible.builtin.copy: src: p10k.zsh dest: ~/.p10k.zsh mode: 0644 |
Note that both files copy the corresponding template for “.zshrc” (depending on starship or p10k, the contents of “.zshrc” are slightly different). For “p10k”, it also copies my theme configuration”; for “starship”, I’m OK with the default prompt. The copied “.zshrc” contains several “aliases” for the command line programs installed by this role (e.g., “ls” is aliased to “exa” commands).
Concerning Molecule, I have several scenarios. As I said, I tested this role in Arch, Ubuntu, and Fedora, so I have a scenario for each operating system. In such cases, I test the “starship” installation and verify that the tools that differ for their installations in different operating systems are installed correctly. This is the “verify.yml” (remember that this installs “starship” and NOT “p10k”, so it ensures that only the former is installed):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
--- - name: Verify hosts: all gather_facts: true tasks: - name: Get current user's shell ansible.builtin.shell: > set -o pipefail && \ grep -E "^{{ ansible_user_id }}:" /etc/passwd | awk -F: '{ print $7 }' register: user_shell args: executable: /bin/bash changed_when: false - name: Assert shell is zsh ansible.builtin.assert: that: "user_shell.stdout == '/bin/zsh'" - name: Assert starship is installed in this scenario ansible.builtin.shell: > starship --version changed_when: false # In Ubuntu we have to create a link to "fd", because # the command is "fdfind", so it's better to check that - name: Assert fd can be found ansible.builtin.shell: > fd --version changed_when: false # In Ubuntu we have to create a link to "bat", because # the command is "batcat", so it's better to check that - name: Assert bat can be found ansible.builtin.shell: > bat --version changed_when: false # In Ubuntu/Fedora we have to install it from archive # so it's better to check that - name: Assert dust can be found ansible.builtin.shell: > dust --version changed_when: false # In Ubuntu we have to install it from archive # so it's better to check that - name: Assert procs can be found ansible.builtin.shell: > procs --version changed_when: false - name: Get current user's home ansible.builtin.shell: > set -o pipefail && \ grep -E "^{{ ansible_user_id }}:" /etc/passwd | awk -F: '{ print $6 }' register: user_home args: executable: /bin/bash changed_when: false - name: Check if ~/.p10k.zsh is present ansible.builtin.stat: path: "{{ user_home.stdout }}/.p10k.zsh" register: result - name: Assert ~/.p10k.zsh is NOT present in this scenario ansible.builtin.assert: that: "not result.stat.exists" |
Concerning “p10k,” I have a separate scenario with a different “verify.yml” (I test this only on Arch since “starship” and “p10k” installations and configurations are the same in all three operating systems):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
--- # This is an example playbook to execute Ansible tests. - name: Verify hosts: all gather_facts: true tasks: - name: Get current user's shell ansible.builtin.shell: > set -o pipefail && \ grep -E "^{{ ansible_user_id }}:" /etc/passwd | awk -F: '{ print $7 }' register: user_shell args: executable: /bin/bash changed_when: false - name: Assert shell is zsh ansible.builtin.assert: that: "user_shell.stdout == '/bin/zsh'" - name: Assert starship is NOT installed in this scenario ansible.builtin.shell: > starship --version register: starship_cmd failed_when: starship_cmd.rc == 0 changed_when: false # In Ubuntu we have to create a link to "fd", because # the command is "fdfind", so it's better to check that - name: Assert fd can be found ansible.builtin.shell: > fd --version changed_when: false # In Ubuntu we have to create a link to "bat", because # the command is "batcat", so it's better to check that - name: Assert bat can be found ansible.builtin.shell: > bat --version changed_when: false # In Ubuntu/Fedora we have to install it from archive # so it's better to check that - name: Assert dust can be found ansible.builtin.shell: > dust --version changed_when: false # In Ubuntu we have to install it from archive # so it's better to check that - name: Assert procs can be found ansible.builtin.shell: > procs --version changed_when: false - name: Get current user's home ansible.builtin.shell: > set -o pipefail && \ grep -E "^{{ ansible_user_id }}:" /etc/passwd | awk -F: '{ print $6 }' register: user_home args: executable: /bin/bash changed_when: false - name: Check if ~/.p10k.zsh is present ansible.builtin.stat: path: "{{ user_home.stdout }}/.p10k.zsh" register: result - name: Assert ~/.p10k.zsh is present ansible.builtin.assert: that: "result.stat.exists" |
However, this “verify.yml” could also be used for the other operating systems since it performs the same verifications concerning installed programs. It differs only for the final part.
Of course, this is tested on GitHub Actions and can be developed directly on the web IDE Gitpod.
I hope you find this post useful for inspiration on how to use Ansible to automatize your Linux installations 🙂
Pingback: Oh My Zsh and Powerlevel10k in macOS | Lorenzo Bettini