Ansible at Home
Using Ansible to automate and manage home environments, including server setups and configurations.
· 6 min read
Introduction #
While some concepts in Engineering should not be brought back to home, I find that Ansible was one of the few tools that is actually useful in a home environment.
Why Ansible? #
Ansible is a powerful automation tool that can help manage and configure systems efficiently. Another key important aspect commonly missed out is documentation. In the past, I would usually SSH into my home server and make changes directly. If I remember to document it, I would save code snippets into a README.md or obsidian note. However, this approach is prone to human error and can lead to inconsistencies over time. Most forms of IaC (Infrastructure as Code) tools are self documenting, as the code itself serves as documentation.
Setup and Configuration #
Before diving into use cases, it’s important to set up Ansible properly for a home environment. The configuration is straightforward and makes running playbooks much more convenient.
ansible.cfg #
Create an ansible.cfg file in your project directory:
[defaults]
inventory = inventory.ini
become_ask_pass = True
The inventory setting tells Ansible where to find your hosts, while become_ask_pass will prompt for your sudo password when needed, which is useful for home setups where you might not want to store credentials. For my home setup, I do not allow passwordless sudo for security reasons.
inventory.ini #
For managing your local machine, create an inventory.ini file:
[local]
localhost ansible_connection=local
This tells Ansible to run commands on your local machine without SSH overhead. You can expand this file later to include other devices on your network.
With these files in place, you can run playbooks with a simple ansible-playbook playbook.yml command, and Ansible will automatically use your configuration.
Use Cases #
System configuration #
I am using a consumer intel CPU with a stock cooler for my homelab. As I don’t expect it to run heavy workloads, I don’t need it to run at full power. Apart from using CPU governors, I can also set the PL1 and PL2 power limits to reduce power consumption and heat generation. I chose not to use the intel-undervolt tool, instead I chose to use Ansible to manage the configuration. This way, if I ever need to re-install the OS or set up a new server, I can easily apply the same configuration without having to remember the exact commands or settings.
---
- name: Configure CPU power limits with turbo boost enabled
hosts: localhost
become: true
vars:
pl1_watts: 65 # Sustained power limit
pl2_watts: 90 # Burst power limit
pl_hard_lower_limit: 51 # Minimum allowed PL
pl_hard_upper_limit: 149 # Maximum allowed PL
tasks:
- name: Validate PL1 power limit range
ansible.builtin.fail:
msg: "pl1_watts must be between {{ pl_hard_lower_limit }} and {{ pl_hard_upper_limit }} watts, got {{ pl1_watts }}"
when: (pl1_watts | int) < (pl_hard_lower_limit | int) or (pl1_watts | int) > (pl_hard_upper_limit | int)
- name: Validate PL2 power limit range
ansible.builtin.fail:
msg: "pl2_watts must be between {{ pl_hard_lower_limit }} and {{ pl_hard_upper_limit }} watts, got {{ pl2_watts }}"
when: (pl2_watts | int) < (pl_hard_lower_limit | int) or (pl2_watts | int) > (pl_hard_upper_limit | int)
- name: Validate PL2 is greater than or equal to PL1
ansible.builtin.fail:
msg: "pl2_watts ({{ pl2_watts }}) must be greater than or equal to pl1_watts ({{ pl1_watts }})"
when: (pl2_watts | int) < (pl1_watts | int)
- name: Check if intel_pstate directory exists
ansible.builtin.stat:
path: /sys/devices/system/cpu/intel_pstate
register: intel_pstate_dir
- name: Check if intel-rapl powercap exists
ansible.builtin.stat:
path: /sys/class/powercap/intel-rapl/intel-rapl:0
register: intel_rapl_dir
- name: Check current turbo boost status
ansible.builtin.slurp:
src: /sys/devices/system/cpu/intel_pstate/no_turbo
register: turbo_status
when: intel_pstate_dir.stat.exists
- name: Enable CPU turbo boost (intel_pstate)
ansible.builtin.shell: echo "0" > /sys/devices/system/cpu/intel_pstate/no_turbo
when:
- intel_pstate_dir.stat.exists
- (turbo_status.content | b64decode | trim) != "0"
changed_when: false
- name: Set PL1 (sustained power limit)
ansible.builtin.shell: |
echo "{{ pl1_watts * 1000000 }}" > /sys/class/powercap/intel-rapl/intel-rapl:0/constraint_0_power_limit_uw
when: intel_rapl_dir.stat.exists
changed_when: false
- name: Set PL2 (burst power limit)
ansible.builtin.shell: |
echo "{{ pl2_watts * 1000000 }}" > /sys/class/powercap/intel-rapl/intel-rapl:0/constraint_1_power_limit_uw
when: intel_rapl_dir.stat.exists
changed_when: false
# For persistence across reboots
- name: Create systemd service for CPU power management
ansible.builtin.copy:
dest: /etc/systemd/system/cpu-power-limits.service
mode: "0644"
owner: root
group: root
content: |
[Unit]
Description=Set CPU Power Limits and Enable Turbo Boost
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'echo "0" > /sys/devices/system/cpu/intel_pstate/no_turbo'
ExecStart=/bin/bash -c 'echo "{{ pl1_watts * 1000000 }}" > /sys/class/powercap/intel-rapl/intel-rapl:0/constraint_0_power_limit_uw'
ExecStart=/bin/bash -c 'echo "{{ pl2_watts * 1000000 }}" > /sys/class/powercap/intel-rapl/intel-rapl:0/constraint_1_power_limit_uw'
[Install]
WantedBy=multi-user.target
when: intel_pstate_dir.stat.exists and intel_rapl_dir.stat.exists
notify:
- Enable and start service
handlers:
- name: Enable and start service
ansible.builtin.systemd:
name: cpu-power-limits
enabled: true
state: started
daemon_reload: true
when: intel_pstate_dir.stat.exists and intel_rapl_dir.stat.exists
Its very easy to make a mistake when setting the power limits. Adding one less zero is not too bad, but adding an extra zero is disastrous. With Ansible, I can validate the input values before applying them, reducing the risk of human error. It’s also much easier to say 65W and 90W, rather than 65000000 and 90000000. I can also set validations to ensure that the values are within acceptable ranges.
Restic setup #
Restic is a great backup tool that can be used to back up data to various locations. The backup scripts are manually written, but the cron jobs and log rotation are managed by Ansible. If the below process were to be done manually, it would be prone to human error and inconsistencies, as multiple files are involved, such as cron jobs, log rotation configuration, and the backup scripts themselves.
---
- name: Setup restic
hosts: localhost
tasks:
- name: Ensure restic exists
ansible.builtin.apt:
name: restic
state: present
cache_valid_time: 3600
become: true
- name: Ensure restic backup script exists
ansible.builtin.stat:
path: "{{ restic_scripts_dir }}"
register: restic_scripts_dir_stat
- name: Check if the folder exists
ansible.builtin.fail:
msg: "The folder {{ restic_scripts_dir }} does not exist. Please create it first."
when: not restic_scripts_dir_stat.stat.exists
- name: Ensure the restic backup script is executable
ansible.builtin.file:
path: "{{ restic_scripts_dir }}/{{ item }}"
mode: '0755'
loop:
- backup_immich.sh
- backup_documents.sh
- name: Create cron job for restic backup immich
ansible.builtin.cron:
name: "Restic Backup Immich"
minute: "0"
hour: "2"
job: "{{ restic_scripts_dir }}/backup_immich.sh >> {{ restic_scripts_dir }}/logs/backup_immich.log 2>&1"
state: present
- name: Create cron job for restic backup documents
ansible.builtin.cron:
name: "Restic Backup Documents"
minute: "0"
hour: "3"
job: "{{ restic_scripts_dir }}/backup_documents.sh >> {{ restic_scripts_dir }}/logs/backup_documents.log 2>&1"
state: present
- name: Setup log rotate for the logs
ansible.builtin.copy:
dest: /etc/logrotate.d/restic-backup
content: |
{{ restic_scripts_dir }}/logs/*.log {
daily
rotate 21
compress
delaycompress
missingok
notifempty
create 644 {{ ansible_user }} {{ ansible_user }}
}
owner: root
group: root
mode: '0644'
validate: "logrotate -d %s"
become: true
Conclusion #
As you add more services and configurations to your home environment, the benefits of using Ansible become even more apparent. It helps maintain consistency, reduces the risk of human error, and serves as documentation for your setup. Whether you’re managing a single server or multiple devices, Ansible can streamline your home automation tasks effectively.