diff --git a/.ansible-lint b/.ansible-lint
index a9f1fd9..799b2c0 100644
--- a/.ansible-lint
+++ b/.ansible-lint
@@ -3,3 +3,4 @@ skip_list:
- 'risky-shell-pipe'
- 'role-name'
- 'no-changed-when'
+ - 'var-naming[no-reserved]'
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..52adca0
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,53 @@
+---
+name: Ansible Lint
+'on':
+ pull_request:
+ push:
+ branches:
+ - main
+
+jobs:
+
+ lint:
+ name: Lint
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out the codebase.
+ uses: actions/checkout@v4
+
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.x'
+
+ - name: Install test dependencies.
+ run: pip3 install yamllint ansible-lint ansible
+
+ - name: Run yamllint.
+ run: yamllint .
+
+ - name: Run ansible-lint.
+ run: ansible-lint
+
+ molecule:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out the codebase.
+ uses: actions/checkout@v4
+
+ - name: Set up Python 3.
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.x'
+
+ - name: Install test dependencies.
+ run: pip3 install ansible molecule molecule-plugins[docker]
+
+ - name: Install role dependencies.
+ run: ansible-galaxy install -r requirements.yml
+
+ - name: Run Molecule tests.
+ run: molecule test --all
+ env:
+ PY_COLORS: '1'
+ ANSIBLE_FORCE_COLOR: '1'
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
deleted file mode 100644
index 9ba3c09..0000000
--- a/.github/workflows/lint.yml
+++ /dev/null
@@ -1,30 +0,0 @@
----
-name: Ansible Lint
-'on':
- pull_request:
- push:
- branches:
- - main
-
-jobs:
-
- lint:
- name: Lint
- runs-on: ubuntu-latest
- steps:
- - name: Check out the codebase.
- uses: actions/checkout@v4
-
- - name: Set up Python 3.x
- uses: actions/setup-python@v5
- with:
- python-version: '3.x'
-
- - name: Install test dependencies.
- run: pip3 install yamllint ansible-lint ansible
-
- - name: Run yamllint.
- run: yamllint .
-
- - name: Run ansible-lint.
- run: ansible-lint
diff --git a/.yamllint b/.yamllint
index 6d56f1b..5ea752b 100644
--- a/.yamllint
+++ b/.yamllint
@@ -3,5 +3,13 @@ extends: default
rules:
line-length:
- max: 200
+ max: 250
level: warning
+ comments:
+ min-spaces-from-content: 1
+ comments-indentation: false
+ braces:
+ max-spaces-inside: 1
+ octal-values:
+ forbid-explicit-octal: true
+ forbid-implicit-octal: true
diff --git a/README.md b/README.md
index 423655c..b9b0cff 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,70 @@
[![Ansible Galaxy Downloads](https://img.shields.io/badge/dynamic/json?color=blueviolet&label=Galaxy%20Downloads&query=%24.download_count&url=https%3A%2F%2Fgalaxy.ansible.com%2Fapi%2Fv1%2Froles%2F39518%2F%3Fformat%3Djson)](https://galaxy.ansible.com/ui/standalone/roles/salvoxia/nut/)
Installs and configures [NUT](http://networkupstools.org/) (Nework UPS
-tools) on Debian based systems.
+tools) on Debian based systems, while allowing for advanced NUT user configuration.
+Also supports installing NUT from source tags to gain access to more up-to-date versions if the system's package manager does not provide them.
+
+__Key Features:__
+ - Set `nut_state` to either install or remove NUT
+ - Allow for advanced NUT user configuration with detailed permission management
+ - Install NUT from a specific source tag instead of package manager in case the package manager provided version is too old
+ - Switch between specific versions installed from source
+ - Switch between installation from package manager and source (or vice versa)
+
+## Installing from Source
+
+By default the role will install NUT using the package manager as determined by the package manager. If the system's package manager comes with an older NUT package, it is possible to install NUT from source. The role will automatically install all build dependencies, check out the desired source version, compile and install NUT from source.
+It is also possible to use this role for updating NUT installed from source to a newer version (or downgrade to an older version). It is not a 'real' upgrade, but the old version is uninstalled before the new version is installed.
+The role supports switching between NUT installed by package manager and installed from source.
+
+The following variables control installation from source:
+
+
+ Variable |
+ Description |
+
+
+
+
+`nut_install_from_source`
+ |
+
+Flag indicating whether to install NUT from source or not. All the variables below have no effect if not set to `true`. Default: `false`
+ |
+
+
+
+
+`nut_source_repository`
+ |
+
+The URL to the Git Repository to compile NUT from. Default: `https://github.com/networkupstools/nut.git`
+ |
+
+
+
+
+`nut_source_tag`
+ |
+
+The Git Tag to check out before compiling NUT. Can be a branch name as well. Default: `v2.8.2`
+ |
+
+
+
+
+`nut_drivers`
+ |
+
+A list of NUT driver names to compile NUT. For a list fo valid drivers refer to the `Drivers` section in the [generic manual for unified NUT drivers](https://networkupstools.org/docs/man/nutupsdrv.html) Example:
+```yaml
+nut_drivers:
+ - snmp-ups
+ - netxml-ups
+```
+ |
+
+
## Role Variables
@@ -250,27 +313,11 @@ All these variables are optional:
-`nut_services`
- |
-
-
-List of service names to enable Default:
-```yaml
-nut_services:
- - nut-driver-enumerator
- - nut-monitor
- - nut-server
-```
- |
-
-
-
-
`nut_enable_service`
|
-Flag indicating whether to start the services defined in `nut_services` after configuration. Default: `true`
+Flag indicating whether to start the services appropriate for the installed `nut_packages` after configuration. Default: `true`
|
@@ -420,7 +467,12 @@ Additional content to append to the `upsmon.conf` file
```
For more examples, please see `tests/test.yml`.
+## Cheat Sheet
+Run a Docker container with systemd:
+```bash
+sudo docker run --tmpfs /tmp --tmpfs /run -v /sys/fs/cgroup:/sys/fs/cgroup:rw --cgroupns=host --privileged --name sysd --rm geerlingguy/docker-debian11-ansible
+```
## License
MIT
diff --git a/defaults/main.yml b/defaults/main.yml
index 5d24714..585f1ba 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -2,17 +2,27 @@
# Whether to start or not the NUT service after the configuration
nut_enable_service: true
+# Indicates whether to install or remove NUT
+nut_state: present
+
+# Flag indicating whether to compile and install NUT from source instead of a package manager
+nut_install_from_source: false
+# Git source repository to use when installing NUT from source
+nut_source_repository: https://github.com/networkupstools/nut.git
+# The Git tag to compile when installing NUT from source
+nut_source_tag: v2.8.2
+# The NUT drivers to configure NUT with when installing from source. This variable only has any effect
+# if nut_install_from_source is set to true.
+nut_drivers: []
+# - snmp-ups
+# - netxml-ups
+
# If this is set to false, none of the following options will have any effect.
# Any and all changes to /etc/nut/* will be your responsibility.
nut_managed_config: true
nut_mode: standalone
-nut_services:
- - nut-driver-enumerator
- - nut-monitor
- - nut-server
-
nut_users:
- name: monitor
password: changeme
diff --git a/handlers/main.yml b/handlers/main.yml
index 2fd77f6..815cc7b 100644
--- a/handlers/main.yml
+++ b/handlers/main.yml
@@ -1,7 +1,8 @@
---
- name: Restart nut
- ansible.builtin.service:
- name: "{{ item }}"
- state: restarted
- with_items: "{{ nut_services }}"
+ ansible.builtin.include_tasks: "../tasks/restart.yml"
when: nut_enable_service
+
+- name: Systemctl daemon-reload
+ ansible.builtin.systemd:
+ daemon_reload: true
diff --git a/molecule/dummy-ups.dev b/molecule/dummy-ups.dev
new file mode 100644
index 0000000..3cf9cd2
--- /dev/null
+++ b/molecule/dummy-ups.dev
@@ -0,0 +1,41 @@
+ambient.humidity: 0.00
+ambient.temperature: 0.0
+battery.charge: 100.00
+battery.charge.low: 20
+battery.runtime: 11520.00
+battery.runtime.low: 180.00
+battery.voltage: 198.00
+device.mfr: Eaton
+device.model: Eaton 9PX
+device.serial: XXXXXXXXXX
+device.type: ups
+driver.name: snmp-ups
+driver.parameter.pollinterval: 2
+driver.parameter.port: XX.XX.XX.XX
+driver.parameter.synchronous: no
+driver.version: 2.7.4
+driver.version.data: mge MIB 0.5
+driver.version.internal: 0.97
+input.phases: 1.00
+input.transfer.reason:
+outlet.desc: Main Outlet
+outlet.id: 0
+output.current: 1.00
+output.frequency: 49.00
+output.phases: 1.00
+output.voltage: 230.00
+ups.beeper.status: enabled
+ups.delay.shutdown: 20
+ups.delay.start: 30
+ups.firmware: 02.14.0034
+ups.firmware.aux: JI
+ups.load: 7.00
+ups.mfr: Eaton
+ups.model: Eaton 9PX
+ups.serial: XXXXXXXXXX
+ups.start.auto: yes
+ups.status: OL
+ups.test.result: aborted
+ups.timer.reboot: -1.00
+ups.timer.shutdown: -1.00
+ups.timer.start: -1.00
diff --git a/molecule/dummy-ups2.dev b/molecule/dummy-ups2.dev
new file mode 100644
index 0000000..3cf9cd2
--- /dev/null
+++ b/molecule/dummy-ups2.dev
@@ -0,0 +1,41 @@
+ambient.humidity: 0.00
+ambient.temperature: 0.0
+battery.charge: 100.00
+battery.charge.low: 20
+battery.runtime: 11520.00
+battery.runtime.low: 180.00
+battery.voltage: 198.00
+device.mfr: Eaton
+device.model: Eaton 9PX
+device.serial: XXXXXXXXXX
+device.type: ups
+driver.name: snmp-ups
+driver.parameter.pollinterval: 2
+driver.parameter.port: XX.XX.XX.XX
+driver.parameter.synchronous: no
+driver.version: 2.7.4
+driver.version.data: mge MIB 0.5
+driver.version.internal: 0.97
+input.phases: 1.00
+input.transfer.reason:
+outlet.desc: Main Outlet
+outlet.id: 0
+output.current: 1.00
+output.frequency: 49.00
+output.phases: 1.00
+output.voltage: 230.00
+ups.beeper.status: enabled
+ups.delay.shutdown: 20
+ups.delay.start: 30
+ups.firmware: 02.14.0034
+ups.firmware.aux: JI
+ups.load: 7.00
+ups.mfr: Eaton
+ups.model: Eaton 9PX
+ups.serial: XXXXXXXXXX
+ups.start.auto: yes
+ups.status: OL
+ups.test.result: aborted
+ups.timer.reboot: -1.00
+ups.timer.shutdown: -1.00
+ups.timer.start: -1.00
diff --git a/molecule/install_from_pkgmgr/converge.yml b/molecule/install_from_pkgmgr/converge.yml
new file mode 100644
index 0000000..45e94c5
--- /dev/null
+++ b/molecule/install_from_pkgmgr/converge.yml
@@ -0,0 +1,40 @@
+---
+- name: Converge
+ hosts: all
+ gather_facts: true
+ pre_tasks:
+ - name: Create NUT configuration folder.
+ ansible.builtin.file:
+ path: "/etc/nut"
+ state: directory
+ mode: '0755'
+ - name: Copy dummy-ups.dev file
+ ansible.builtin.copy:
+ src: "../dummy-ups.dev"
+ dest: "/etc/nut/dummy-ups.dev"
+ mode: '0755'
+ - name: Copy dummy-ups2.dev file
+ ansible.builtin.copy:
+ src: "../dummy-ups2.dev"
+ dest: "/etc/nut/dummy-ups2.dev"
+ mode: '0755'
+ roles:
+ - role: salvoxia.nut
+ nut_users:
+ - name: monitor
+ password: changeme
+ role: secondary
+ - name: second_user
+ password: insecure_password
+ role: primary
+ nut_ups:
+ - name: dummy_ups
+ monitoruser: monitor
+ driver: dummy-ups
+ device: /etc/nut/dummy-ups.dev
+ description: "Dummy UPS for testing"
+ - name: dummy_ups2
+ monitoruser: second_user
+ driver: dummy-ups
+ device: /etc/nut/dummy-ups2.dev
+ description: "Dummy UPS 2 for testing"
diff --git a/molecule/install_from_pkgmgr/molecule.yml b/molecule/install_from_pkgmgr/molecule.yml
new file mode 100644
index 0000000..095fc37
--- /dev/null
+++ b/molecule/install_from_pkgmgr/molecule.yml
@@ -0,0 +1,24 @@
+---
+driver:
+ name: docker
+platforms:
+ - name: debian-12
+ image: geerlingguy/docker-debian12-ansible
+ command: ${MOLECULE_DOCKER_COMMAND:-""}
+ pre_build_image: true
+ cgroupns_mode: host
+ privileged: true
+ volumes:
+ - /sys/fs/cgroup:/sys/fs/cgroup:rw
+ - name: debian-11
+ image: geerlingguy/docker-debian11-ansible
+ command: ${MOLECULE_DOCKER_COMMAND:-""}
+ pre_build_image: true
+ cgroupns_mode: host
+ privileged: true
+ volumes:
+ - /sys/fs/cgroup:/sys/fs/cgroup:rw
+provisioner:
+ name: ansible
+verifier:
+ name: ansible
diff --git a/molecule/install_from_pkgmgr/verify.yml b/molecule/install_from_pkgmgr/verify.yml
new file mode 100644
index 0000000..574f0e7
--- /dev/null
+++ b/molecule/install_from_pkgmgr/verify.yml
@@ -0,0 +1,51 @@
+---
+- name: Converge
+ hosts: all
+ gather_facts: true
+ tasks:
+ - name: Verify communication with dummy_ups
+ ansible.builtin.command:
+ argv:
+ - upsc
+ - dummy_ups
+
+ - name: Perform command on dummy_ups with monitor user
+ ansible.builtin.command:
+ argv:
+ - upscmd
+ - -a monitor
+ - -p changeme
+ - dummy_ups
+ - load.off
+
+ - name: Perform command on dummy_ups2 with monitor user
+ ansible.builtin.command:
+ argv:
+ - upscmd
+ - -a monitor
+ - -p changeme
+ - dummy_ups2
+ - load.off
+ - name: Verify communication with dummy_ups2
+ ansible.builtin.command:
+ argv:
+ - upsc
+ - dummy_ups2
+
+ - name: Perform command on dummy_ups with second_user user
+ ansible.builtin.command:
+ argv:
+ - upscmd
+ - -a second_user
+ - -p insecure_password
+ - dummy_ups
+ - load.off
+
+ - name: Perform command on dummy_ups2 with second_user user
+ ansible.builtin.command:
+ argv:
+ - upscmd
+ - -a second_user
+ - -p insecure_password
+ - dummy_ups2
+ - load.off
diff --git a/molecule/install_from_source/converge.yml b/molecule/install_from_source/converge.yml
new file mode 100644
index 0000000..f7f4de7
--- /dev/null
+++ b/molecule/install_from_source/converge.yml
@@ -0,0 +1,31 @@
+---
+- name: Converge
+ hosts: all
+ gather_facts: true
+ pre_tasks:
+ - name: Create NUT configuration folder.
+ ansible.builtin.file:
+ path: "/etc/nut"
+ state: directory
+ mode: '0755'
+ - name: Copy dummy-ups.dev file
+ ansible.builtin.copy:
+ src: "../dummy-ups.dev"
+ dest: "/etc/nut/dummy-ups.dev"
+ mode: '0755'
+ roles:
+ - role: salvoxia.nut
+ nut_install_from_source: true
+ nut_source_tag: v2.8.2
+ nut_drivers:
+ - dummy-ups
+ nut_users:
+ - name: monitor
+ password: changeme
+ role: secondary
+ nut_ups:
+ - name: dummy_ups
+ monitoruser: monitor
+ driver: dummy-ups
+ device: /etc/nut/dummy-ups.dev
+ description: "Dummy UPS for testing"
diff --git a/molecule/install_from_source/molecule.yml b/molecule/install_from_source/molecule.yml
new file mode 100644
index 0000000..095fc37
--- /dev/null
+++ b/molecule/install_from_source/molecule.yml
@@ -0,0 +1,24 @@
+---
+driver:
+ name: docker
+platforms:
+ - name: debian-12
+ image: geerlingguy/docker-debian12-ansible
+ command: ${MOLECULE_DOCKER_COMMAND:-""}
+ pre_build_image: true
+ cgroupns_mode: host
+ privileged: true
+ volumes:
+ - /sys/fs/cgroup:/sys/fs/cgroup:rw
+ - name: debian-11
+ image: geerlingguy/docker-debian11-ansible
+ command: ${MOLECULE_DOCKER_COMMAND:-""}
+ pre_build_image: true
+ cgroupns_mode: host
+ privileged: true
+ volumes:
+ - /sys/fs/cgroup:/sys/fs/cgroup:rw
+provisioner:
+ name: ansible
+verifier:
+ name: ansible
diff --git a/molecule/install_from_source/verify.yml b/molecule/install_from_source/verify.yml
new file mode 100644
index 0000000..57d84d5
--- /dev/null
+++ b/molecule/install_from_source/verify.yml
@@ -0,0 +1,49 @@
+---
+- name: Converge
+ hosts: all
+ gather_facts: true
+ tasks:
+ - name: Verify communication with dummy_ups
+ ansible.builtin.command:
+ argv:
+ - upsc
+ - dummy_ups
+
+ - name: Perform command on dummy_ups with monitor user
+ ansible.builtin.command:
+ argv:
+ - upscmd
+ - -a monitor
+ - -p changeme
+ - dummy_ups
+ - load.off
+
+ - name: Get installed NUT version
+ ansible.builtin.include_tasks: ../../tasks/get_installed_nut_version.yml
+
+ - name: Verify installed version matches desired version
+ ansible.builtin.assert:
+ that:
+ - nut_installed_version is defined
+ - nut_installed_version == "v2.8.2"
+
+ - name: Gather installed services
+ ansible.builtin.service_facts:
+
+ - name: Verify NUT services are running
+ ansible.builtin.assert:
+ that:
+ - ansible_facts.services['nut-monitor.service'] is defined
+ - ansible_facts.services['nut-server.service'] is defined
+ - ansible_facts.services['nut-monitor.service'].state == 'running'
+ - ansible_facts.services['nut-server.service'].state == 'running'
+
+ - name: Gather package facts
+ ansible.builtin.package_facts:
+ when: nut_installed_version is defined
+
+ - name: Verify no NUT packages are installed
+ ansible.builtin.assert:
+ that:
+ - ansible_facts.packages['nut-client'] is not defined
+ - ansible_facts.packages['nut-server'] is not defined
diff --git a/molecule/switch_from_pkgmgr_to_source/converge.yml b/molecule/switch_from_pkgmgr_to_source/converge.yml
new file mode 100644
index 0000000..f7f4de7
--- /dev/null
+++ b/molecule/switch_from_pkgmgr_to_source/converge.yml
@@ -0,0 +1,31 @@
+---
+- name: Converge
+ hosts: all
+ gather_facts: true
+ pre_tasks:
+ - name: Create NUT configuration folder.
+ ansible.builtin.file:
+ path: "/etc/nut"
+ state: directory
+ mode: '0755'
+ - name: Copy dummy-ups.dev file
+ ansible.builtin.copy:
+ src: "../dummy-ups.dev"
+ dest: "/etc/nut/dummy-ups.dev"
+ mode: '0755'
+ roles:
+ - role: salvoxia.nut
+ nut_install_from_source: true
+ nut_source_tag: v2.8.2
+ nut_drivers:
+ - dummy-ups
+ nut_users:
+ - name: monitor
+ password: changeme
+ role: secondary
+ nut_ups:
+ - name: dummy_ups
+ monitoruser: monitor
+ driver: dummy-ups
+ device: /etc/nut/dummy-ups.dev
+ description: "Dummy UPS for testing"
diff --git a/molecule/switch_from_pkgmgr_to_source/molecule.yml b/molecule/switch_from_pkgmgr_to_source/molecule.yml
new file mode 100644
index 0000000..095fc37
--- /dev/null
+++ b/molecule/switch_from_pkgmgr_to_source/molecule.yml
@@ -0,0 +1,24 @@
+---
+driver:
+ name: docker
+platforms:
+ - name: debian-12
+ image: geerlingguy/docker-debian12-ansible
+ command: ${MOLECULE_DOCKER_COMMAND:-""}
+ pre_build_image: true
+ cgroupns_mode: host
+ privileged: true
+ volumes:
+ - /sys/fs/cgroup:/sys/fs/cgroup:rw
+ - name: debian-11
+ image: geerlingguy/docker-debian11-ansible
+ command: ${MOLECULE_DOCKER_COMMAND:-""}
+ pre_build_image: true
+ cgroupns_mode: host
+ privileged: true
+ volumes:
+ - /sys/fs/cgroup:/sys/fs/cgroup:rw
+provisioner:
+ name: ansible
+verifier:
+ name: ansible
diff --git a/molecule/switch_from_pkgmgr_to_source/prepare.yml b/molecule/switch_from_pkgmgr_to_source/prepare.yml
new file mode 100644
index 0000000..45e94c5
--- /dev/null
+++ b/molecule/switch_from_pkgmgr_to_source/prepare.yml
@@ -0,0 +1,40 @@
+---
+- name: Converge
+ hosts: all
+ gather_facts: true
+ pre_tasks:
+ - name: Create NUT configuration folder.
+ ansible.builtin.file:
+ path: "/etc/nut"
+ state: directory
+ mode: '0755'
+ - name: Copy dummy-ups.dev file
+ ansible.builtin.copy:
+ src: "../dummy-ups.dev"
+ dest: "/etc/nut/dummy-ups.dev"
+ mode: '0755'
+ - name: Copy dummy-ups2.dev file
+ ansible.builtin.copy:
+ src: "../dummy-ups2.dev"
+ dest: "/etc/nut/dummy-ups2.dev"
+ mode: '0755'
+ roles:
+ - role: salvoxia.nut
+ nut_users:
+ - name: monitor
+ password: changeme
+ role: secondary
+ - name: second_user
+ password: insecure_password
+ role: primary
+ nut_ups:
+ - name: dummy_ups
+ monitoruser: monitor
+ driver: dummy-ups
+ device: /etc/nut/dummy-ups.dev
+ description: "Dummy UPS for testing"
+ - name: dummy_ups2
+ monitoruser: second_user
+ driver: dummy-ups
+ device: /etc/nut/dummy-ups2.dev
+ description: "Dummy UPS 2 for testing"
diff --git a/molecule/switch_from_pkgmgr_to_source/verify.yml b/molecule/switch_from_pkgmgr_to_source/verify.yml
new file mode 100644
index 0000000..57d84d5
--- /dev/null
+++ b/molecule/switch_from_pkgmgr_to_source/verify.yml
@@ -0,0 +1,49 @@
+---
+- name: Converge
+ hosts: all
+ gather_facts: true
+ tasks:
+ - name: Verify communication with dummy_ups
+ ansible.builtin.command:
+ argv:
+ - upsc
+ - dummy_ups
+
+ - name: Perform command on dummy_ups with monitor user
+ ansible.builtin.command:
+ argv:
+ - upscmd
+ - -a monitor
+ - -p changeme
+ - dummy_ups
+ - load.off
+
+ - name: Get installed NUT version
+ ansible.builtin.include_tasks: ../../tasks/get_installed_nut_version.yml
+
+ - name: Verify installed version matches desired version
+ ansible.builtin.assert:
+ that:
+ - nut_installed_version is defined
+ - nut_installed_version == "v2.8.2"
+
+ - name: Gather installed services
+ ansible.builtin.service_facts:
+
+ - name: Verify NUT services are running
+ ansible.builtin.assert:
+ that:
+ - ansible_facts.services['nut-monitor.service'] is defined
+ - ansible_facts.services['nut-server.service'] is defined
+ - ansible_facts.services['nut-monitor.service'].state == 'running'
+ - ansible_facts.services['nut-server.service'].state == 'running'
+
+ - name: Gather package facts
+ ansible.builtin.package_facts:
+ when: nut_installed_version is defined
+
+ - name: Verify no NUT packages are installed
+ ansible.builtin.assert:
+ that:
+ - ansible_facts.packages['nut-client'] is not defined
+ - ansible_facts.packages['nut-server'] is not defined
diff --git a/molecule/switch_from_source_to_pkgmgr/converge.yml b/molecule/switch_from_source_to_pkgmgr/converge.yml
new file mode 100644
index 0000000..36bd365
--- /dev/null
+++ b/molecule/switch_from_source_to_pkgmgr/converge.yml
@@ -0,0 +1,40 @@
+---
+- name: Uninstall NUT
+ hosts: all
+ gather_facts: true
+ pre_tasks:
+ - name: Create NUT configuration folder.
+ ansible.builtin.file:
+ path: "/etc/nut"
+ state: directory
+ mode: '0755'
+ - name: Copy dummy-ups.dev file
+ ansible.builtin.copy:
+ src: "../dummy-ups.dev"
+ dest: "/etc/nut/dummy-ups.dev"
+ mode: '0755'
+ - name: Copy dummy-ups2.dev file
+ ansible.builtin.copy:
+ src: "../dummy-ups2.dev"
+ dest: "/etc/nut/dummy-ups2.dev"
+ mode: '0755'
+ roles:
+ - role: salvoxia.nut
+ nut_users:
+ - name: monitor
+ password: changeme
+ role: secondary
+ - name: second_user
+ password: insecure_password
+ role: primary
+ nut_ups:
+ - name: dummy_ups
+ monitoruser: monitor
+ driver: dummy-ups
+ device: /etc/nut/dummy-ups.dev
+ description: "Dummy UPS for testing"
+ - name: dummy_ups2
+ monitoruser: second_user
+ driver: dummy-ups
+ device: /etc/nut/dummy-ups2.dev
+ description: "Dummy UPS 2 for testing"
diff --git a/molecule/switch_from_source_to_pkgmgr/molecule.yml b/molecule/switch_from_source_to_pkgmgr/molecule.yml
new file mode 100644
index 0000000..095fc37
--- /dev/null
+++ b/molecule/switch_from_source_to_pkgmgr/molecule.yml
@@ -0,0 +1,24 @@
+---
+driver:
+ name: docker
+platforms:
+ - name: debian-12
+ image: geerlingguy/docker-debian12-ansible
+ command: ${MOLECULE_DOCKER_COMMAND:-""}
+ pre_build_image: true
+ cgroupns_mode: host
+ privileged: true
+ volumes:
+ - /sys/fs/cgroup:/sys/fs/cgroup:rw
+ - name: debian-11
+ image: geerlingguy/docker-debian11-ansible
+ command: ${MOLECULE_DOCKER_COMMAND:-""}
+ pre_build_image: true
+ cgroupns_mode: host
+ privileged: true
+ volumes:
+ - /sys/fs/cgroup:/sys/fs/cgroup:rw
+provisioner:
+ name: ansible
+verifier:
+ name: ansible
diff --git a/molecule/switch_from_source_to_pkgmgr/prepare.yml b/molecule/switch_from_source_to_pkgmgr/prepare.yml
new file mode 100644
index 0000000..1bb91e2
--- /dev/null
+++ b/molecule/switch_from_source_to_pkgmgr/prepare.yml
@@ -0,0 +1,19 @@
+---
+- name: Converge
+ hosts: all
+ gather_facts: true
+ roles:
+ - role: salvoxia.nut
+ nut_install_from_source: false
+ nut_drivers:
+ - dummy-ups
+ nut_users:
+ - name: monitor
+ password: changeme
+ role: secondary
+ nut_ups:
+ - name: dummy_ups
+ monitoruser: monitor
+ driver: dummy-ups
+ device: /etc/nut/dummy-ups.dev
+ description: "Dummy UPS for testing"
diff --git a/molecule/switch_from_source_to_pkgmgr/verify.yml b/molecule/switch_from_source_to_pkgmgr/verify.yml
new file mode 100644
index 0000000..59cff38
--- /dev/null
+++ b/molecule/switch_from_source_to_pkgmgr/verify.yml
@@ -0,0 +1,60 @@
+---
+- name: Converge
+ hosts: all
+ gather_facts: true
+ tasks:
+ - name: Verify communication with dummy_ups
+ ansible.builtin.command:
+ argv:
+ - upsc
+ - dummy_ups
+
+ - name: Perform command on dummy_ups with monitor user
+ ansible.builtin.command:
+ argv:
+ - upscmd
+ - -a monitor
+ - -p changeme
+ - dummy_ups
+ - load.off
+
+ - name: Perform command on dummy_ups2 with monitor user
+ ansible.builtin.command:
+ argv:
+ - upscmd
+ - -a monitor
+ - -p changeme
+ - dummy_ups2
+ - load.off
+ - name: Verify communication with dummy_ups2
+ ansible.builtin.command:
+ argv:
+ - upsc
+ - dummy_ups2
+
+ - name: Perform command on dummy_ups with second_user user
+ ansible.builtin.command:
+ argv:
+ - upscmd
+ - -a second_user
+ - -p insecure_password
+ - dummy_ups
+ - load.off
+
+ - name: Perform command on dummy_ups2 with second_user user
+ ansible.builtin.command:
+ argv:
+ - upscmd
+ - -a second_user
+ - -p insecure_password
+ - dummy_ups2
+ - load.off
+
+ - name: Gather package facts
+ ansible.builtin.package_facts:
+
+ - name: Verify NUT packages are installed
+ ansible.builtin.assert:
+ that:
+ - ansible_facts.packages['nut-client'] is defined
+ - ansible_facts.packages['nut-server'] is defined
diff --git a/molecule/uninstall_from_pkgmgr/converge.yml b/molecule/uninstall_from_pkgmgr/converge.yml
new file mode 100644
index 0000000..74b747e
--- /dev/null
+++ b/molecule/uninstall_from_pkgmgr/converge.yml
@@ -0,0 +1,7 @@
+---
+- name: Converge part two
+ hosts: all
+ gather_facts: true
+ roles:
+ - role: salvoxia.nut
+ nut_state: absent
diff --git a/molecule/uninstall_from_pkgmgr/molecule.yml b/molecule/uninstall_from_pkgmgr/molecule.yml
new file mode 100644
index 0000000..095fc37
--- /dev/null
+++ b/molecule/uninstall_from_pkgmgr/molecule.yml
@@ -0,0 +1,24 @@
+---
+driver:
+ name: docker
+platforms:
+ - name: debian-12
+ image: geerlingguy/docker-debian12-ansible
+ command: ${MOLECULE_DOCKER_COMMAND:-""}
+ pre_build_image: true
+ cgroupns_mode: host
+ privileged: true
+ volumes:
+ - /sys/fs/cgroup:/sys/fs/cgroup:rw
+ - name: debian-11
+ image: geerlingguy/docker-debian11-ansible
+ command: ${MOLECULE_DOCKER_COMMAND:-""}
+ pre_build_image: true
+ cgroupns_mode: host
+ privileged: true
+ volumes:
+ - /sys/fs/cgroup:/sys/fs/cgroup:rw
+provisioner:
+ name: ansible
+verifier:
+ name: ansible
diff --git a/molecule/uninstall_from_pkgmgr/prepare.yml b/molecule/uninstall_from_pkgmgr/prepare.yml
new file mode 100644
index 0000000..45e94c5
--- /dev/null
+++ b/molecule/uninstall_from_pkgmgr/prepare.yml
@@ -0,0 +1,40 @@
+---
+- name: Converge
+ hosts: all
+ gather_facts: true
+ pre_tasks:
+ - name: Create NUT configuration folder.
+ ansible.builtin.file:
+ path: "/etc/nut"
+ state: directory
+ mode: '0755'
+ - name: Copy dummy-ups.dev file
+ ansible.builtin.copy:
+ src: "../dummy-ups.dev"
+ dest: "/etc/nut/dummy-ups.dev"
+ mode: '0755'
+ - name: Copy dummy-ups2.dev file
+ ansible.builtin.copy:
+ src: "../dummy-ups2.dev"
+ dest: "/etc/nut/dummy-ups2.dev"
+ mode: '0755'
+ roles:
+ - role: salvoxia.nut
+ nut_users:
+ - name: monitor
+ password: changeme
+ role: secondary
+ - name: second_user
+ password: insecure_password
+ role: primary
+ nut_ups:
+ - name: dummy_ups
+ monitoruser: monitor
+ driver: dummy-ups
+ device: /etc/nut/dummy-ups.dev
+ description: "Dummy UPS for testing"
+ - name: dummy_ups2
+ monitoruser: second_user
+ driver: dummy-ups
+ device: /etc/nut/dummy-ups2.dev
+ description: "Dummy UPS 2 for testing"
diff --git a/molecule/uninstall_from_pkgmgr/verify.yml b/molecule/uninstall_from_pkgmgr/verify.yml
new file mode 100644
index 0000000..584f3b7
--- /dev/null
+++ b/molecule/uninstall_from_pkgmgr/verify.yml
@@ -0,0 +1,36 @@
+---
+- name: Converge
+ hosts: all
+ gather_facts: true
+ tasks:
+ - name: Check if upsc is in PATH
+ ansible.builtin.command: which upsc
+ changed_when: false
+ failed_when: nut_install_check_result.rc not in [0, 1]
+ register: nut_install_check_result
+
+ - name: Verify that NUT is not installed
+ ansible.builtin.assert:
+ that:
+ - nut_install_check_result.rc == 1
+
+ - name: Gather installed services
+ ansible.builtin.service_facts:
+
+ - name: Verify NUT services are not present
+ ansible.builtin.assert:
+ that:
+ - ansible_facts.services['nut-monitor.service'] is undefined
+ - ansible_facts.services['nut-server.service'] is undefined
+ - ansible_facts.services['nut-driver.service'] is undefined
+ - ansible_facts.services['nut-driver-enumerator.service'] is undefined
+
+ - name: Check if NUT configuration exists
+ ansible.builtin.stat:
+ path: /etc/nut
+ register: register_name
+
+ - name: Verify NUT configuration folder does not exist
+ ansible.builtin.assert:
+ that:
+ - register_name.stat.exists is false
diff --git a/molecule/uninstall_from_source/converge.yml b/molecule/uninstall_from_source/converge.yml
new file mode 100644
index 0000000..0418905
--- /dev/null
+++ b/molecule/uninstall_from_source/converge.yml
@@ -0,0 +1,7 @@
+---
+- name: Uninstall NUT
+ hosts: all
+ gather_facts: true
+ roles:
+ - role: salvoxia.nut
+ nut_state: absent
diff --git a/molecule/uninstall_from_source/molecule.yml b/molecule/uninstall_from_source/molecule.yml
new file mode 100644
index 0000000..095fc37
--- /dev/null
+++ b/molecule/uninstall_from_source/molecule.yml
@@ -0,0 +1,24 @@
+---
+driver:
+ name: docker
+platforms:
+ - name: debian-12
+ image: geerlingguy/docker-debian12-ansible
+ command: ${MOLECULE_DOCKER_COMMAND:-""}
+ pre_build_image: true
+ cgroupns_mode: host
+ privileged: true
+ volumes:
+ - /sys/fs/cgroup:/sys/fs/cgroup:rw
+ - name: debian-11
+ image: geerlingguy/docker-debian11-ansible
+ command: ${MOLECULE_DOCKER_COMMAND:-""}
+ pre_build_image: true
+ cgroupns_mode: host
+ privileged: true
+ volumes:
+ - /sys/fs/cgroup:/sys/fs/cgroup:rw
+provisioner:
+ name: ansible
+verifier:
+ name: ansible
diff --git a/molecule/uninstall_from_source/prepare.yml b/molecule/uninstall_from_source/prepare.yml
new file mode 100644
index 0000000..f7f4de7
--- /dev/null
+++ b/molecule/uninstall_from_source/prepare.yml
@@ -0,0 +1,31 @@
+---
+- name: Converge
+ hosts: all
+ gather_facts: true
+ pre_tasks:
+ - name: Create NUT configuration folder.
+ ansible.builtin.file:
+ path: "/etc/nut"
+ state: directory
+ mode: '0755'
+ - name: Copy dummy-ups.dev file
+ ansible.builtin.copy:
+ src: "../dummy-ups.dev"
+ dest: "/etc/nut/dummy-ups.dev"
+ mode: '0755'
+ roles:
+ - role: salvoxia.nut
+ nut_install_from_source: true
+ nut_source_tag: v2.8.2
+ nut_drivers:
+ - dummy-ups
+ nut_users:
+ - name: monitor
+ password: changeme
+ role: secondary
+ nut_ups:
+ - name: dummy_ups
+ monitoruser: monitor
+ driver: dummy-ups
+ device: /etc/nut/dummy-ups.dev
+ description: "Dummy UPS for testing"
diff --git a/molecule/uninstall_from_source/verify.yml b/molecule/uninstall_from_source/verify.yml
new file mode 100644
index 0000000..584f3b7
--- /dev/null
+++ b/molecule/uninstall_from_source/verify.yml
@@ -0,0 +1,36 @@
+---
+- name: Converge
+ hosts: all
+ gather_facts: true
+ tasks:
+ - name: Check if upsc is in PATH
+ ansible.builtin.command: which upsc
+ changed_when: false
+ failed_when: nut_install_check_result.rc not in [0, 1]
+ register: nut_install_check_result
+
+ - name: Verify that NUT is not installed
+ ansible.builtin.assert:
+ that:
+ - nut_install_check_result.rc == 1
+
+ - name: Gather installed services
+ ansible.builtin.service_facts:
+
+ - name: Verify NUT services are not present
+ ansible.builtin.assert:
+ that:
+ - ansible_facts.services['nut-monitor.service'] is undefined
+ - ansible_facts.services['nut-server.service'] is undefined
+ - ansible_facts.services['nut-driver.service'] is undefined
+ - ansible_facts.services['nut-driver-enumerator.service'] is undefined
+
+ - name: Check if NUT configuration exists
+ ansible.builtin.stat:
+ path: /etc/nut
+ register: register_name
+
+ - name: Verify NUT configuration folder does not exist
+ ansible.builtin.assert:
+ that:
+ - register_name.stat.exists is false
diff --git a/requirements.yml b/requirements.yml
new file mode 100644
index 0000000..4a0d0c3
--- /dev/null
+++ b/requirements.yml
@@ -0,0 +1,3 @@
+---
+collections:
+ - name: ansible.utils
diff --git a/tasks/assert.yml b/tasks/assert.yml
index f39a7de..e762492 100644
--- a/tasks/assert.yml
+++ b/tasks/assert.yml
@@ -1,6 +1,13 @@
---
+- name: Verify nut_state
+ ansible.builtin.assert:
+ that: nut_state in ['present', 'absent']
+ fail_msg: Variable nut_state must be one of ['present', 'absent']!
+
- name: Verify variables for generating configuration
- when: nut_managed_config is defined and nut_managed_config
+ when: nut_state == 'present'
+ and nut_managed_config is defined
+ and nut_managed_config
block:
- name: Assert NUT mode
ansible.builtin.assert:
diff --git a/tasks/compile_and_install_or_uninstall_from_source.yml b/tasks/compile_and_install_or_uninstall_from_source.yml
new file mode 100644
index 0000000..d062b8e
--- /dev/null
+++ b/tasks/compile_and_install_or_uninstall_from_source.yml
@@ -0,0 +1,83 @@
+# This task performs a Git Clone and checkout operation on the desired source tag,
+# runs "configure", "make" and finally "make install" or "make uninstall".
+# Sources are removed in the end.
+#
+# It does NOT use the role's main variables but a subset:
+# - nut_source_install_repository: The Git repository to compile NUT from
+# - nut_source_install_dir: The working directory for cloning into and compiling in
+# - nut_source_install_tag: The Git tag/branch to check out before installing
+# - nut_source_install_state: 'present' to install or 'absent' to uninstall
+---
+- name: Get and configure NUT sources
+ block:
+ - name: Initialize nut_source_install_repository
+ ansible.builtin.set_fact:
+ nut_source_install_repository: "{{ nut_source_repository }}"
+ when: nut_source_install_repository is not defined
+
+ - name: Initialize nut_source_install_tag
+ ansible.builtin.set_fact:
+ nut_source_install_tag: "{{ nut_source_tag }}"
+ when: nut_source_install_tag is not defined
+
+ - name: Git checkout
+ ansible.builtin.git:
+ repo: '{{ nut_source_install_repository }}'
+ dest: "{{ nut_source_install_dir.path }}/nut"
+ version: "{{ nut_source_install_tag }}"
+
+ - name: Run NUT autogen
+ ansible.builtin.command:
+ chdir: "{{ nut_source_install_dir.path }}/nut"
+ cmd: "./autogen.sh"
+ changed_when: true
+
+ - name: Gather Architecture
+ ansible.builtin.command:
+ cmd: "gcc -print-multiarch"
+ changed_when: true
+ register: gcc_multiarch
+
+ - name: Run NUT configure
+ ansible.builtin.command:
+ chdir: "{{ nut_source_install_dir.path }}/nut"
+ cmd: |
+ ./configure --prefix=/usr
+ --includedir=/usr/include
+ --infodir=/usr/share/info --sysconfdir=/etc/nut --localstatedir=/var
+ --libexecdir=/usr/lib/nut --srcdir=.
+ --disable-silent-rules --libdir=/usr/lib/{{ gcc_multiarch.stdout }}
+ --with-ssl --with-nss {{ nut_configure_with_drivers }} --enable-static
+ --with-statepath=/var/run/nut --with-altpidpath=/var/run/nut
+ --with-drvpath=/lib/nut --with-pidpath=/var/run/nut
+ --datadir=/usr/share/nut
+ --with-user=nut --with-group=nut --with-udev-dir=/lib/udev
+ --with-systemdsystemunitdir=yes --with-systemdshutdowndir --with-docs=no
+ changed_when: true
+
+- name: Install NUT from source
+ when: nut_source_install_state == 'present'
+ block:
+ - name: Make NUT
+ community.general.make:
+ chdir: "{{ nut_source_install_dir.path }}/nut"
+
+ - name: Install NUT
+ community.general.make:
+ chdir: "{{ nut_source_install_dir.path }}/nut"
+ target: install
+ become: true
+
+- name: Uninstall NUT from source
+ when: nut_source_install_state == 'absent'
+ block:
+ - name: Uninstall NUT
+ community.general.make:
+ chdir: "{{ nut_source_install_dir.path }}/nut"
+ target: uninstall
+ become: true
+
+- name: Remove NUT sources
+ ansible.builtin.file:
+ path: "{{ nut_source_install_dir.path }}"
+ state: absent
diff --git a/tasks/configure.yml b/tasks/configure.yml
new file mode 100644
index 0000000..68f105b
--- /dev/null
+++ b/tasks/configure.yml
@@ -0,0 +1,79 @@
+# This task copies the NUT configuration files and performs
+# templating using the role's variables
+---
+- name: Ensure NUT group exists
+ ansible.builtin.group:
+ name: nut
+ state: present
+
+- name: Ensure NUT user exists
+ ansible.builtin.user:
+ name: nut
+ group: nut
+ state: present
+
+- name: Create NUT configuration folder.
+ ansible.builtin.file:
+ path: "/etc/nut"
+ state: directory
+ owner: root
+ group: nut
+ mode: '0755'
+ when: nut_managed_config
+
+- name: Install common NUT configuration files.
+ ansible.builtin.template:
+ src: "{{ item }}.j2"
+ dest: "/etc/nut/{{ item }}"
+ owner: root
+ group: nut
+ mode: '0640'
+ with_items:
+ - nut.conf
+ - upsmon.conf
+ - upssched.conf
+ notify: Restart nut
+ when: nut_managed_config
+
+- name: Install NUT configuration files.
+ ansible.builtin.template:
+ src: "{{ item }}.j2"
+ dest: "/etc/nut/{{ item }}"
+ owner: root
+ group: nut
+ mode: '0640'
+ with_items:
+ - ups.conf
+ - upsd.users
+ - upsd.conf
+ notify: Restart nut
+ when: nut_managed_config and 'nut-server' in nut_packages
+
+- name: Install custom notifycmd script.
+ ansible.builtin.copy:
+ dest: "{{ nut_upsmon_notifycmd }}"
+ content: "{{ nut_upsmon_notifycmd_content }}"
+ owner: root
+ group: nut
+ mode: '0770'
+ when: nut_upsmon_notifycmd_content | length > 0
+
+- name: Install custom upssched-cmd script.
+ ansible.builtin.copy:
+ dest: "{{ nut_upssched_cmdscript }}"
+ content: "{{ nut_upssched_cmdscript_content }}"
+ owner: root
+ group: nut
+ mode: '0770'
+ when: nut_upssched_cmdscript_content | length > 0
+
+- name: Create upssched folders
+ ansible.builtin.file:
+ path: "{{ item | dirname }}"
+ owner: nut
+ group: nut
+ mode: '0770'
+ recurse: true
+ with_items:
+ - "{{ nut_upssched_pipefn }}"
+ - "{{ nut_upssched_lockfn }}"
diff --git a/tasks/delete_service.yml b/tasks/delete_service.yml
new file mode 100644
index 0000000..e367db1
--- /dev/null
+++ b/tasks/delete_service.yml
@@ -0,0 +1,14 @@
+---
+- name: Determine service unit file
+ ansible.builtin.systemd_service:
+ name: "{{ service_name }}"
+ register: service_status
+ when: nut_state == "absent"
+
+- name: Remove service unit files
+ ansible.builtin.file:
+ state: absent
+ path: "{{ service_status.status['FragmentPath'] }}"
+ notify:
+ - Systemctl daemon-reload
+ when: nut_state == "absent" and service_status.status['FragmentPath'] is defined
diff --git a/tasks/get_installed_nut_services.yml b/tasks/get_installed_nut_services.yml
new file mode 100644
index 0000000..992023b
--- /dev/null
+++ b/tasks/get_installed_nut_services.yml
@@ -0,0 +1,7 @@
+---
+- name: Populate service facts
+ ansible.builtin.service_facts:
+
+- name: "Find existing NUT services."
+ ansible.builtin.set_fact:
+ nut_services_installed: "{{ ansible_facts['services'].values() | selectattr('status', 'defined') | selectattr('name', 'search', '^nut-.*') | list }}"
diff --git a/tasks/get_installed_nut_version.yml b/tasks/get_installed_nut_version.yml
new file mode 100644
index 0000000..ed0868d
--- /dev/null
+++ b/tasks/get_installed_nut_version.yml
@@ -0,0 +1,27 @@
+# This determines if NUT is installed and sets
+# a fact 'nut_installed_version' with the format 'v..' if it is.
+# After running this task, check if NUT is installed by checking "nut_installed_version is defined"
+---
+- name: Determine if NUT must be removed from source to install desired version
+ block:
+ - name: Check if NUT is installed
+ ansible.builtin.command: which upsc
+ changed_when: false
+ failed_when: nut_install_check_result.rc not in [0,1]
+ register: nut_install_check_result
+
+ - name: Evaluating check result
+ ansible.builtin.set_fact:
+ nut_installed: "{{ nut_install_check_result.rc in [0] }}"
+
+ - name: Get installed NUT version
+ ansible.builtin.command: upsc -h
+ changed_when: false
+ register: nut_upsc_help_result
+ when: nut_installed
+
+ - name: Extract NUT version
+ ansible.builtin.set_fact:
+ # nut_upsc_help_result.stdout_lines[0] could look like this: "Network UPS Tools upsc 2.8.0-signed"
+ nut_installed_version: "v{{ nut_upsc_help_result.stdout_lines[0] | regex_search('.*(\\d+\\.\\d+.\\d).*', '\\1') | first }}"
+ when: nut_installed
diff --git a/tasks/install_managed.yml b/tasks/install_managed.yml
new file mode 100644
index 0000000..8b5ea6b
--- /dev/null
+++ b/tasks/install_managed.yml
@@ -0,0 +1,33 @@
+---
+- name: Get installed NUT version
+ ansible.builtin.include_tasks: get_installed_nut_version.yml
+
+- name: Gather the package facts
+ ansible.builtin.package_facts:
+ when: nut_installed_version is defined
+
+- name: Uninstall NUT from source
+ when: "nut_installed_version is defined and ansible_facts.packages['nut-client'] is not defined or nut_state == 'absent'"
+ block:
+ - name: Set uninstall variables
+ ansible.builtin.set_fact:
+ nut_state: absent
+ nut_install_from_source: true
+
+ - name: Uninstall NUT from source
+ ansible.builtin.include_tasks: manage_install_from_source.yml
+
+ - name: Reset install variables
+ ansible.builtin.set_fact:
+ nut_state: present
+ nut_install_from_source: false
+
+- name: Ensure NUT packages are installed.
+ ansible.builtin.package:
+ name: "{{ nut_packages }}"
+ update_cache: true
+ state: present
+
+- name: Configure NUT
+ ansible.builtin.include_tasks: configure.yml
+ when: nut_state == 'present' and nut_managed_config
diff --git a/tasks/main.yml b/tasks/main.yml
index 58b1eec..f9a7bde 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -10,64 +10,22 @@
nut_packages: "{{ __nut_packages | list }}"
when: nut_packages is not defined
-- name: Ensure NUT packages are installed.
- ansible.builtin.package:
- name: "{{ nut_packages }}"
- state: present
+- name: Install NUT via package manager
+ ansible.builtin.include_tasks: install_managed.yml
+ when: nut_state == 'present' and not nut_install_from_source
-- name: Install common NUT configuration files.
- ansible.builtin.template:
- src: "{{ item }}.j2"
- dest: "/etc/nut/{{ item }}"
- owner: root
- group: nut
- mode: '0640'
- with_items:
- - nut.conf
- - upsmon.conf
- - upssched.conf
- notify: Restart nut
- when: nut_managed_config
+- name: Install or uninstall NUT from source
+ ansible.builtin.include_tasks: manage_install_from_source.yml
+ when: nut_state == 'present' and nut_install_from_source
-- name: Install NUT configuration files.
- ansible.builtin.template:
- src: "{{ item }}.j2"
- dest: "/etc/nut/{{ item }}"
- owner: root
- group: nut
- mode: '0640'
- with_items:
- - ups.conf
- - upsd.users
- - upsd.conf
- notify: Restart nut
- when: nut_managed_config and 'nut-server' in nut_packages
+- name: Configure NUT services
+ ansible.builtin.include_tasks: service.yml
+ when: nut_state == "present"
-- name: Install custom notifycmd script.
- ansible.builtin.copy:
- dest: "{{ nut_upsmon_notifycmd }}"
- content: "{{ nut_upsmon_notifycmd_content }}"
- owner: root
- group: nut
- mode: '0770'
- when: nut_upsmon_notifycmd_content | length > 0
+- name: Uninstall NUT via package manager
+ ansible.builtin.include_tasks: uninstall.yml
+ when: nut_state == 'absent'
-- name: Install custom upssched-cmd script.
- ansible.builtin.copy:
- dest: "{{ nut_upssched_cmdscript }}"
- content: "{{ nut_upssched_cmdscript_content }}"
- owner: root
- group: nut
- mode: '0770'
- when: nut_upssched_cmdscript_content | length > 0
-
-- name: Create upssched folders
- ansible.builtin.file:
- path: "{{ item | dirname }}"
- owner: root
- group: nut
- mode: '0770'
- recurse: true
- with_items:
- - "{{ nut_upssched_pipefn }}"
- - "{{ nut_upssched_lockfn }}"
+- name: Remove NUT configuration
+ ansible.builtin.include_tasks: unconfigure.yml
+ when: nut_state == 'absent' and nut_managed_config
diff --git a/tasks/manage_install_from_source.yml b/tasks/manage_install_from_source.yml
new file mode 100644
index 0000000..65f6254
--- /dev/null
+++ b/tasks/manage_install_from_source.yml
@@ -0,0 +1,154 @@
+# This task assumes it is only called if nut_install_from_source is true!
+# It does not include this check again!
+# For nut_state == present it performs the following actions
+# - remove NUT if installed via package manager
+# - check if NUT is already installed and determine version
+# - if the wrong version is installed, it will uninstall it from source
+# - install the desired version from source
+# - install and enable the desired services#
+#
+# For nut_state == absent it performs the following actions
+# - remove NUT if installed via package manager
+# - check if NUT is already installed and determine version
+# - remove NUT from source if installed
+# - uninstall build dependencies
+---
+- name: Get installed NUT version
+ ansible.builtin.include_tasks: get_installed_nut_version.yml
+
+- name: Gather package facts
+ ansible.builtin.package_facts:
+ when: nut_installed
+
+- name: Ensure NUT is not installed via package manager
+ ansible.builtin.include_tasks: uninstall.yml
+ when: ansible_facts.packages['nut-client'] is defined
+
+- name: Initialize nut_reinstall to false
+ ansible.builtin.set_fact:
+ nut_reinstall: false
+
+- name: Get installed NUT version
+ ansible.builtin.include_tasks: get_installed_nut_version.yml
+
+- name: Set nut_reinstall to true if desired version not installed
+ ansible.builtin.set_fact:
+ nut_reinstall: true
+ when: nut_installed and nut_installed_version is defined and nut_installed_version != nut_source_tag
+
+- name: Install build dependencies
+ when: "nut_reinstall
+ or (nut_state == 'present' and not nut_installed)
+ or (nut_state == 'absent' and nut_installed)"
+ block:
+ - name: Install mk-build-deps
+ ansible.builtin.package:
+ name:
+ - devscripts
+ - equivs
+ - git
+ update_cache: true
+ state: present
+
+ - name: Create temp folder for NUT
+ ansible.builtin.tempfile:
+ state: directory
+ prefix: nut
+ register: nut_dir
+
+ - name: Ensure debian source repository is added to sources.list
+ ansible.builtin.apt_repository:
+ repo: deb-src http://deb.debian.org/debian {{ ansible_distribution_release }} main
+ state: present
+
+ - name: Create dependency package and install build dependencies
+ ansible.builtin.command: mk-build-deps nut-server
+ args:
+ chdir: "{{ nut_dir.path }}"
+ become: true
+ changed_when: true
+ register: build_deps
+
+ - name: Parse package name for build dependencies
+ ansible.utils.cli_parse:
+ text: "{{ build_deps.stdout }}"
+ parser:
+ name: ansible.netcommon.native
+ template_path: "{{ role_path }}/templates/mk-build-deps_output.yml"
+ set_fact: build_deps_kg
+
+ - name: Install build dependencies
+ ansible.builtin.package:
+ deb: "{{ nut_dir.path }}/{{ build_deps_kg['build-deps-package'] }}"
+
+- name: Build list of NUT drivers
+ when: "'nut-server' in nut_packages and nut_reinstall
+ or (nut_state == 'present' and not nut_installed)
+ or (nut_state == 'absent' and nut_installed)"
+ block:
+ - name: Add drivers based one package name
+ ansible.builtin.set_fact:
+ nut_drivers: "{{ nut_drivers | union(nut_packages_to_drivers | selectattr(item, 'defined') | map(attribute=item)) }}"
+ when: nut_packages_to_drivers | selectattr(item, 'defined') | list | count > 0
+ with_items: "{{ nut_packages }}"
+
+ - name: Add dummy-ups driver to nut-drivers for uninstall purposes
+ ansible.builtin.set_fact:
+ nut_drivers:
+ - dummy-ups
+ when: nut_state == 'absent' and nut_drivers | length == 0
+
+ - name: Verify at least one driver is selected
+ ansible.builtin.assert:
+ that: nut_drivers | count > 0
+ fail_msg: At least one NUT driver must be selected either directly by specifying it in nut_drivers or indirectly by adding a driver package to nut_packages if nut-server is selected for installation!
+
+ - name: Create --with-drivers configure argument
+ ansible.builtin.set_fact:
+ nut_configure_with_drivers: "--with-drivers={{ nut_drivers | flatten | unique | join(',') }}"
+
+- name: Uninstall old NUT version
+ when: nut_reinstall or (nut_state == 'absent' and nut_installed)
+ block:
+ - name: Set uninstall variables
+ ansible.builtin.set_fact:
+ nut_source_install_dir: "{{ nut_dir }}"
+ nut_source_install_state: absent
+ nut_source_install_tag: "{{ nut_installed_version }}"
+ nut_source_install_repository: "{{ nut_source_repository }}"
+
+ - name: Uninstall NUT from source
+ ansible.builtin.include_tasks: compile_and_install_or_uninstall_from_source.yml
+
+- name: Install desired NUT version
+ when: nut_reinstall or (nut_state == 'present' and not nut_installed)
+ block:
+ - name: Set uninstall variables
+ ansible.builtin.set_fact:
+ nut_source_install_dir: "{{ nut_dir }}"
+ nut_source_install_state: present
+ nut_source_install_tag: "{{ nut_source_tag }}"
+ nut_source_install_repository: "{{ nut_source_repository }}"
+
+ - name: Install NUT from source
+ ansible.builtin.include_tasks: compile_and_install_or_uninstall_from_source.yml
+
+- name: Configure NUT
+ ansible.builtin.include_tasks: configure.yml
+ when: nut_state == 'present' and nut_managed_config
+
+- name: Uninstall build dependencies
+ ansible.builtin.package:
+ name: nut-build-deps
+ state: absent
+ when: nut_state == 'absent'
+
+- name: Define NUT services to enable
+ ansible.builtin.set_fact:
+ nut_services:
+ - nut-client.service
+
+- name: Add NUT services for NUT server
+ ansible.builtin.set_fact:
+ nut_services: "{{ nut_services + ['nut-server.service', 'nut-driver.service'] }}"
+ when: "'nut-server' in nut_packages"
diff --git a/tasks/restart.yml b/tasks/restart.yml
new file mode 100644
index 0000000..2c50e44
--- /dev/null
+++ b/tasks/restart.yml
@@ -0,0 +1,11 @@
+---
+- name: Get installed NUT services
+ ansible.builtin.include_tasks: get_installed_nut_services.yml
+ when: nut_enable_service
+
+- name: "Restart nut."
+ ansible.builtin.service:
+ name: "{{ item }}"
+ state: restarted
+ with_items: "{{ nut_services_installed | map(attribute='name') | list | difference(['nut-driver.service', 'nut-driver@.service']) }}"
+ when: nut_enable_service
diff --git a/tasks/service.yml b/tasks/service.yml
new file mode 100644
index 0000000..4bd2e99
--- /dev/null
+++ b/tasks/service.yml
@@ -0,0 +1,51 @@
+---
+- name: Get service facts
+ ansible.builtin.service_facts:
+ when: nut_services is defined
+
+- name: Get installed NUT services
+ ansible.builtin.include_tasks: get_installed_nut_services.yml
+ when: nut_services is not defined or nut_state == 'absent'
+
+- name: Populate nut_services
+ ansible.builtin.set_fact:
+ nut_services: "{{ nut_services_installed }}"
+ when: nut_services is not defined
+
+- name: Create and enable NUT services
+ when: nut_state == "present" and nut_enable_service
+ block:
+ - name: Enable systemd services
+ ansible.builtin.service:
+ name: "{{ item }}"
+ state: started
+ enabled: true
+ when: "nut_services is not undefined
+ and ansible_facts.services[item] is defined
+ and ansible_facts.services[item].status != 'running'"
+ loop: "{{ nut_services }}"
+
+- name: Disable and remove NUT services service
+ when: nut_state == "absent" or not nut_enable_service
+ block:
+ - name: Stop and disable services
+ ansible.builtin.service:
+ state: stopped
+ enabled: false
+ name: "{{ item.name }}"
+ notify:
+ - Systemctl daemon-reload
+ when: item.name != 'nut-driver@.service'
+ and item.status not in ['not-found', 'failed']
+ loop: "{{ nut_services_installed }}"
+
+ - name: Update installed NUT services
+ ansible.builtin.include_tasks: get_installed_nut_services.yml
+
+ - name: Delete services
+ ansible.builtin.include_tasks: delete_service.yml
+ loop: "{{ nut_services_installed | rejectattr('status', 'equalto', 'masked') | map(attribute='name') | list }}"
+ loop_control:
+ loop_var: service_name
+ when: nut_state == 'absent'
+ and service_name != 'nut-driver@.service'
diff --git a/tasks/unconfigure.yml b/tasks/unconfigure.yml
new file mode 100644
index 0000000..7669bb8
--- /dev/null
+++ b/tasks/unconfigure.yml
@@ -0,0 +1,18 @@
+# This task removes the NUT configuration files
+---
+- name: Remove NUT configuration files
+ ansible.builtin.file:
+ path: /etc/nut
+ state: absent
+ when: nut_state == 'absent' and nut_managed_config
+
+- name: Ensure NUT user is removed
+ ansible.builtin.user:
+ name: nut
+ group: nut
+ state: absent
+
+- name: Ensure NUT group is removed
+ ansible.builtin.group:
+ name: nut
+ state: absent
diff --git a/tasks/uninstall.yml b/tasks/uninstall.yml
new file mode 100644
index 0000000..9c048be
--- /dev/null
+++ b/tasks/uninstall.yml
@@ -0,0 +1,45 @@
+---
+- name: Get installed NUT version
+ ansible.builtin.include_tasks: get_installed_nut_version.yml
+
+- name: Gather the package facts
+ ansible.builtin.package_facts:
+ when: nut_installed_version is defined
+
+- name: Disable services
+ when: nut_installed_version is defined
+ block:
+ - name: Populate service facts
+ ansible.builtin.service_facts:
+
+ - name: "Find existing NUT services."
+ ansible.builtin.set_fact:
+ nut_services_existing: "{{ ansible_facts['services'].values() | selectattr('status', 'defined') | rejectattr('status', 'equalto', 'masked') | selectattr('name', 'search', '^nut-.*') | list }}"
+
+ - name: Stop and disable services
+ ansible.builtin.service:
+ state: stopped
+ enabled: false
+ name: "{{ item.name }}"
+ notify:
+ - Systemctl daemon-reload
+ when: item.name != 'nut-driver@.service'
+ loop: "{{ nut_services_existing }}"
+
+- name: Uninstall NUT from source
+ when: "nut_installed_version is defined and ansible_facts.packages['nut-client'] is not defined"
+ block:
+ - name: Set uninstall variables
+ ansible.builtin.set_fact:
+ nut_state: absent
+ nut_install_from_source: true
+
+ - name: Uninstall NUT from source
+ ansible.builtin.include_tasks: manage_install_from_source.yml
+
+- name: Remove NUT via package manager
+ when: "nut_installed_version is defined and ansible_facts.packages['nut-client'] is defined"
+ ansible.builtin.include_tasks: uninstall_managed.yml
+
+- name: Uninstall NUT services
+ ansible.builtin.include_tasks: service.yml
diff --git a/tasks/uninstall_managed.yml b/tasks/uninstall_managed.yml
new file mode 100644
index 0000000..98c8515
--- /dev/null
+++ b/tasks/uninstall_managed.yml
@@ -0,0 +1,10 @@
+---
+- name: Remove NUT packages
+ ansible.builtin.apt:
+ purge: true
+ name: "{{ nut_packages }}"
+ state: absent
+
+- name: Perform autoremove (to remove libupsclient6)
+ ansible.builtin.apt:
+ autoremove: true
diff --git a/templates/mk-build-deps_output.yml b/templates/mk-build-deps_output.yml
new file mode 100644
index 0000000..bcfe6be
--- /dev/null
+++ b/templates/mk-build-deps_output.yml
@@ -0,0 +1,6 @@
+---
+- example: "dpkg-deb: building package 'nut-build-deps' in '../nut-build-deps_2.8.0-7_amd64.deb'."
+ getval: "dpkg-deb: building package 'nut-build-deps' in '\\.\\.\\/(?P\\S+)'\\."
+ result:
+ build-deps-package: "{{ pkgname }}"
+ shared: true
diff --git a/vars/Debian.yml b/vars/Debian.yml
index 333a060..2f5fcfb 100644
--- a/vars/Debian.yml
+++ b/vars/Debian.yml
@@ -2,4 +2,22 @@
__nut_packages:
- nut-client
- nut-server
- - nut-monitor
+
+nut_packages_to_drivers:
+ - nut-i2c:
+ - asem
+ - nut-ipmi:
+ - nut-ipmipsu
+ - nut-modbus:
+ - adelsystem_cbi
+ - apc_modbus
+ - generic_modbus
+ - huawei-ups2000
+ - phoenixcontact_modbus
+ - socomec_jbus
+ - nut-powerman-pdu:
+ - powerman-pdu
+ - nut-snmp:
+ - snmp-ups
+ - nut-xml:
+ - netxml-ups