Bootstrapping a fresh Linux install with Ansible

Bootstrapping a fresh Linux install with Ansible

Time 5 min read

Updated 


Ansible is an IT tool that enables Infrastructure as Code, letting you automate provisioning, configuration, management and deployment of services and applications. I like using it at a fraction of it's full power to bootstrap fresh installs of Linux for my homelab.

Table of Contents

  1. Install Ansible
  2. The configuration file
  3. The inventory file
  4. The playbook
  5. Running the playbook
  6. References

This is not a comprehensive tutorial for Ansible, but simply a terse quick guide of my personal use case for Ansible in my home lab. For a deeper dive on Ansible, I strongly suggest Learn Linux TV series of Ansible tutorials which is how I first learned to use it myself.

Install Ansible

First install Ansible via package manager. Note that there’s actually two packages to choose from: ansible-core is very minimal and only comes with a small set of modules and plugins, while ansible is a larger “batteries included” package with that comes with many Ansible Collections. The playbook I use and which I’ll discuss below only uses built-in modules included in ansible-core, but if you plan to make more {{ ansible_user }}vanced playbooks, you should probably just install ansible from the start.

Ansible is available in some, but not all package managers. If it’s not available via your distribution’s package manager, see the Ansible documentation for other ways.

The below will assume you’re using Debian or Ubuntu, which first requires adding the Ansible PPA:

sudo add-apt-repository ppa:ansible/ansible
sudo apt install ansible -y

The configuration file

For configuration, Ansible uses a ansible.cfg file, which uses INI syntax. A base config exists at /etc/ansible/ansible.cfg, but you can create a project-specific config file and any changes you make there will supercedes the base config. It’s not required, but without it you have to pass a bunch of options when executing the playbook, like --private_key_file to specify an SSH key to use. Here is mine:

# ansible.cfg

[defaults]

inventory = hosts.yaml
private_key_file = ~/.ssh/id_ed25519
retry_files_enabled = False
ansible_python_interpreter = /usr/bin/python3
timeout = 30

[privilege_escalation]

become = True
become_method = sudo
become_user = root
become_ask_pass = False

[ssh_connections]

pipelining = True
scp_if_ssh = True

Some of these should be fairly self-explanatory. scp_if_ssh speeds up file copying a little and pipelining = true vastly improves the speed at which Ansible executes tasks.

The inventory file

Next we need the inventory file, which can be in either YAML or INI synxtax, but I’m much more comfortable working with YAML, so that’s what I use.

# hosts.yaml
---
all:
 vars:
  ansible_user: # user
  ansible_connection: ssh

 children:
  servers:
   hosts:
    athena:
     ansible_host: # ip address
    korben:
     ansible_host: # ip address
    zima:
     ansible_host: # ip address

  mini:
   hosts:
    potato:
     ansible_host: # ip address
    spud:
     ansible_host: # ip address

  pc:
   hosts:
    apollo:
     ansible_host: # ip address
    loki:
     ansible_host: # ip address

  remote:
   hosts:
    outpost:
     ansible_host: # ip address
    bastion:
     ansible_host: # ip address

This inventory is divided into different groups of hosts, and the playbook can target specific ones — for example only server hosts get Cockpit installed and only pc hosts (a desktop and a laptop) get Google Chrome. I use the same username on all my machines, so I pass it via the {{ ansible_user }} variable.

The playbook

The below playbook is what I use to bootstrap my Linux machines. It’s pretty basic, compared with all that Ansible can do, but set up my machines up with essential packages and some of my custom configurations. Copies of dotfiles and configs for SMB, Git and more are kept in a files subdirectory within the Ansible repo. The playbook backs up the existing files on the target machine and copy over my custom ones.

# bootstrap.yaml

- name: Bootstrap Linux Server
  vars:
   user: # user

  tasks:
   - name: Safe upgrade of all installed packages
     apt:
      update_cache: yes
      upgrade: safe
      cache_valid_time: 86400

   - name: Install various apt packages
     apt:
      update_cache: yes
      name: # list packages to install
       - zsh
       - git
       - sudo
       - curl
       - wget
       - rsync
       - net-tools
       - smartmontools
       - samba
       - cifs-utils
       - nfs-kernel-server
      state: present
      install_recommends: yes

   - name: Clean cache & remove unnecessary dependencies
     apt:
      autoclean: yes
      autoremove: yes

   - name: Copy the Samba config file
     copy:
      src: files/smb
      dest: /etc/samba/smb.conf

   - name: Start the Samba daemon
     service:
      name: smbd
      state: started

   - name: Start the NetBIOs daemon
     service:
      name: nmbd
      state: started

   - name: Enable the Samba daemon
     ansible.builtin.systemd:
      name: smbd
      enabled: yes

   - name: Enable the NetBIOS daemon
     ansible.builtin.systemd:
      name: nmbd
      enabled: yes

   - name: Backup default SMB config & copy over custom SMB config
     copy:
      src: files/samba/smb.conf
      dest: /etc/samba/smb.conf

   - name: Copy the Git config file
     copy:
      src: files/git/.gitconfig
      dest: '/home/{{ ansible_user }}/.gitconfig'

   - name: Backup default Nano config & copy custom Nano config
     copy:
      src: files/nano/nanorc
      dest: /etc/nanorc
      mode: '0644'
      backup: yes

   - name: Copy .zshrc file
     copy:
      src: files/zshrc
      dest: '/home/{{ ansible_user }}/.zshrc'
      owner: user
      group: group
      mode: 0644

   - name: Copy .aliases file
     copy:
      src: files/aliases
      dest: '/home/{{ ansible_user }}/.aliases'
      owner: user
      group: group
      mode: 0644

   - name: Set the default shell
     user:
      name: '{{ ansible_user }}'
      shell: 'zsh'

   - name: Check if reboot is required
     stat:
      path: /var/run/reboot-required
     register: reboot_required_file

   - name: Reboot if required
     reboot:
      msg: Rebooting due to a kernel update
     when: reboot_required_file.stat.exists

This playbook installs a selection of packages I commonly use, changes the default shell from bash to zsh, copies over dotfiles and other configs, and start/enables Samba.

Running the playbook

All that’s left now is to run the playbook, but first it’s highly recommended to do a dry-run that runs the playbook in check mode, to verify it works without making any changes. From within the same directory as all the Ansible files, use the following command:

ansible-playbook bootstrap.yaml --check

You’ll see some output as the playbook carries out its tasks, and if there’s any errors it will clearly say so and cancel.

If all looks good and there’s no errors, you can run the playbook for real:

ansible-playbook bootstrap.yaml

References


Webmentions

Loading mentions...