:. Update and cleanup pkgs with Ansible on OpenBSD

We used Ansible to manage users on before, today we'll use Ansible for something more daily, which is update packages on the system, if you are using OpenBSD current this can be very useful for you.

:. Prerequisites

As always, make sure you have:
1. a OpenBSD server.
2. Ansible installed on your machine ($ doas pkg_add ansible).
3. Python installed on the OpenBSD server (at this time the Python version is 3.13, $ doas pkg_add python).

:. Setting Up the Inventory File

If you didn't kept yours from the other article, the inventory file is typically named `hosts` and it lists the servers that Ansible will manage plus the ansible_python_interpreter plus many other fun things:

$ cat hosts

[current:vars]
ansible_python_interpreter=/usr/local/bin/python3.13

[release:vars]
ansible_python_interpreter=/usr/local/bin/python3.12

[current]
ports
nextcloud
apu01
rock64

[release]
serv00
dns01

In this file:

- [current:vars] : This section sets variables for the `current` group.

- ansible_python_interpreter=/usr/local/bin/python3.13 : This specifies where Python is located on the OpenBSD server.

- [current]/[release] : This section lists the IP addresses of the servers in the `current` and `release` group. If you have a proper entry on your ssh_config(5) you can just use that hostname since Ansible will connect to it over ssh.

:. The simple ansible playbook

The playbook Ansible playbook that will create the user:

$ cat update_pkgs.yml
---
- name: OpenBSD package update + cleanup
  hosts: current
  become: true
  become_method: doas # Uses doas to become root

  tasks:
    - name: Update all packages
      community.general.openbsd_pkg:
        name: '*'
        state: latest
      register: update_result # Saves the module's result so we can read its .msg later

    - name: Clean up orphaned packages
      command: pkg_delete -a
      register: cleanup_result
      changed_when: "'No packages to delete' not in cleanup_result.stdout"
      # ↑ Prevents Ansible from marking the task as "changed" every time when
      #   pkg_delete -a finds nothing to remove (makes output much cleaner)

    - name: Show update summary
      debug:
        msg: "{{ update_result.msg | default('No changes or no output') }}"
        # ↑ Uses .msg from the openbsd_pkg module if it exists,
        #   otherwise shows a friendly fallback message instead of empty output

    - name: Show cleanup summary
      debug:
        msg: "{{ cleanup_result.stdout | default('No packages removed') }}"
        # ↑ Shows whatever pkg_delete printed, or a message if nothing happened

If you don’t want to give the user running Ansible passwordless access via doas(8) (for example, by adding a permit nopass rule in /etc/doas.conf), and you’d rather use your regular user password, Ansible Vault (or see the here) will do the trick. It lets you encrypt the become password so it’s never stored in plain text in your playbooks.

:. Running the Playbook

To run the playbook, we use the following:

$ ansible-playbook -i hosts update_pkgs.yml
PLAY [OpenBSD package update + cleanup] *****************************************************

TASK [Gathering Facts] **********************************************************************
ok: [ports]
ok: [nextcloud]
ok: [apu01]
ok: [rock64]

TASK [Update all packages] ******************************************************************
changed: [ports]
changed: [nextcloud]
changed: [apu01]
changed: [rock64]

TASK [Clean up orphaned packages] ***********************************************************
changed: [ports]
changed: [nextcloud]
changed: [apu01]
changed: [rock64]

TASK [Show update summary] ******************************************************************
ok: [ports] => {
    "msg": "No changes or no output"
}
ok: [nextcloud] => {
    "msg": "No changes or no output"
}
ok: [apu01] => {
    "msg": "No changes or no output"
}
ok: [rock64] => {
    "msg": "No changes or no output"
}

TASK [Show cleanup summary] ****************************************************************
ok: [ports] => {
    "msg": ""
}
ok: [nextcloud] => {
    "msg": ""
}
ok: [apu01] => {
    "msg": ""
}
ok: [rock64] => {
    "msg": ""
}

PLAY RECAP *********************************************************************************
ports      : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
nextcloud  : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
apu01      : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
rock64     : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

:. Conclusion

Life is too short to upgrade all the packages by hand so using Ansible to manage it can be very useful and easy once you do all the effort writing down the playbook. You can add syspatch(8) also to the playbook if you are using -release.