This repository contains my Ansible code (collections, roles, playbooks, inventories, etc) for setting up/maintaining my homelab. The project is driven by nox
-based session automation.
- Python
nox
and Ansible both depend on Python.- (Optional) Use pyenv to install Python.
virtualenv
Python package- Install with pip:
pip install --user virtualenv
- Install with
pipx
:pipx install virtualenv
- Install with pip:
- Create virtual environment with
virtualenv .venv
- Activate environment
- Linux:
. .venv/bin/activate
- Windows:
. .venv\Scripts\activate
- Linux:
- Install requirements
pip install -r requirements.yml
- Install Ansible requirements
TODO:
- Document generating SSH keypairs
- Document the
~/.ssh/config
file - Document the
ansible_svc
SSH account - Document the
create-ansible-svc-user.yml
playbook
TODO:
- Document using
ansible-playbook
to run plays in./plays/
- Document using
ansible-galaxy {collection,role} init
to create new collections/roles. - Document using
ansible-galaxy build
to build collections. - Document running
nox
sessions defined innoxfile.py
- Document adding new sesions
The inventories/
path is where server infrastructure can be configured. Use an inventory's group_vars/all.yml
to set variables for the inventory group, then use ansible-playbook -i inventories/<inventory_name>/inventory.yml ...
.
To create a new inventory, create a new directory and add inventory and vars files:
mkdir inventories/new_inventory/group_vars
- Create the inventory directory, "new_inventory/", and the
group_vars/
directory, where variables for the inventory group are set.
- Create the inventory directory, "new_inventory/", and the
touch inventories/new_inventory/inventory.yml
- The inventory file where hosts are declared and grouped.
- When setting a variable for an inventory, check the Ansible documentation to determine where to set and override variables: Variable precedence: Where should I put a variable?.
touch inventories/new_inventory/group_vars/all.yml
- Create the group variables file, where you can define variables that apply to the whole inventory.
Example inventory.yml
---
## Define a cluster group, with 2 agents and 2 servers
all:
hosts:
cl-ag1:
ansible_host: "192.168.1.61"
cl-ag2:
ansible_host: "192.168.1.62"
cl-srv1:
ansible_host: "192.168.1.60"
cl-srv2:
ansible_host: "192.168.1.64"
## Variables set here apply to the hosts above across all inventory groups
vars:
## Set remote user for all hosts
ansible_user: "ubuntu"
## Create a group specifically for onboarding into Ansible management.
onboard:
hosts:
## Re-declare hosts from above. Variables like ansible_host and ansible_user are inherited from the "all" group.
rpi-cl-ag1:
rpi-cl-ag2:
rpi-cl-srv1:
rpi-cl-srv2:
## Vars set here will only apply when ansible-playbook -i ... --limit onboard is used
vars:
## Set private key to use for connections.
ansible_ssh_private_key_file: "~/.ssh/ansible_id_rsa"
## Set public key variable, which is used in the onboard play
ansible_ssh_public_key_file: "~/.ssh/ansible_id_rsa.pub"
Playbooks join collections and roles into repeatable steps/tasks that can be applied to an inventory.
For example, the plays/maint/update-system.yml
playbook will run apt
or dnf
updates/upgrades, and reboot the remote host if required after an update. The playbook uses variables from the inventory's group_vars/all.yml
file, and pulls in the update_system
role from the my.homelab
collection.
Collections are a distribution format for Ansible content that can include playbooks, roles, modules, and plugins. You can install and use collections through a distribution server, such as Ansible Galaxy, or a Pulp 3 Galaxy server.
Collections in ./collections/my/
are built & installed using the `requirements.yml file. Any time a collection changes, it must be rebuilt.
The noxfile.py
included in this repository has some pre-defined sessions for automating repetitive tasks like building collections. The CUSTOM_COLLECTIONS
list in noxfile.py
must be updated if/when new collections are added to the my
namespace.
When nox
is called with no parameters, the following sessions are executed:
lint
: Thenoxfile
is linted withruff
.build-my-collections
: The script loops over all collections in [./collections/my/](./collections/my/) and builds each one with
ansible-galaxy collection build`.- The built distribution file is outputted to
./build/
, a directory which is ignored in.gitignore
and will be created if it does not already exist.
- The built distribution file is outputted to
install-ansible-requirements
: Installs collections and roles defined in./requirements.yml
.
The noxfile.py
file defines nox
sessions to automate tasks like building & installing collections, and executing some playbooks. Note that for the playbook sessions, your host must already be configured with an SSH connection to the remote host (see SSH setup for instructions).
When adding a new Ansible collection, if you want nox
to handle building & installing the collection when you make changes, you must create an instance of CustomAnsibleCollection()
and add it to the CUSTOM_COLLECTIONS
list. In the example below, we will add a new collection named "my.base_setup" to the automated build list:
## noxfile.py
# other nox code
...
## Note: do not make changes to this class, just take note of the parameters
# for when you create an instance of this object in CUSTOM_COLLECTIONS
@dataclass
class CustomAnsibleCollection:
"""Define a custom Ansible collection for Nox sessions.
Params:
name (str): The simple name of the role, mainly used for logging messages.
fqcn (str): The fully-qualified collection name, i.e. `my.collection`
path (Path): The local file path to the Ansible collection.
"""
name: str = field(default=None)
fqcn: str = field(default=None)
path: Path | None = field(default=None)
@property
def path_exists(self) -> bool:
if self.path:
return self.path.exists()
else:
return False
def __post_init__(self):
if self.path:
_path: Path = Path(f"{self.path}")
self.path = _path
_log = logging.getLogger("nox.CustomAnsibleCollection")
self.logger = _log
CUSTOM_COLLECTIONS: t.List[CustomAnsibleCollection] = [
## Existing collection named "my.homelab"
CustomAnsibleCollection(name="homelab", fqcn="my.homelab", path=Path(f"{MY_COLLECTIONS_PATH}/homelab")),
## New collection, we will initialize it on the fly. You could also create a variable for the class and use
# CUSTOM_COLLECTIONS.append(custom_collection)
CustomAnsibleCollection(name="base_setup", fcqn="my.base_setup", path=Path(f"{MY_COLLECTIONS_PATH}/base_setup"))
]
TODO:
- Document existing sessions & usage
- Document adding new sessions