From 5218fa27142f1c9c17499a4a9d1454d8892a10b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20D=C3=A9siles?= <1536672+cdesiles@users.noreply.github.com> Date: Sat, 5 Sep 2026 17:24:12 +0200 Subject: [PATCH] refactor(nut): merge prometheus exporter into nut role - exporter gated by nut_prometheus_exporter_enabled - env-file config (correct NUT_EXPORTER_* vars + --web.listen-address) - remove standalone nut_exporter role --- roles/nut/README.md | 27 ++++++++- roles/nut/defaults/main.yml | 11 +++- roles/nut/handlers/main.yml | 9 +++ roles/nut/tasks/exporter.yml | 32 ++++++++++ roles/nut/tasks/main.yml | 4 ++ roles/nut/templates/nut-exporter-env.conf.j2 | 5 ++ roles/nut/vars/archlinux.yml | 5 ++ roles/nut_exporter/README.md | 60 ------------------- roles/nut_exporter/defaults/main.yml | 13 ---- roles/nut_exporter/handlers/main.yml | 9 --- roles/nut_exporter/meta/main.yml | 2 - roles/nut_exporter/tasks/main.yml | 46 -------------- roles/nut_exporter/templates/override.conf.j2 | 10 ---- roles/nut_exporter/vars/archlinux.yml | 6 -- 14 files changed, 88 insertions(+), 151 deletions(-) create mode 100644 roles/nut/tasks/exporter.yml create mode 100644 roles/nut/templates/nut-exporter-env.conf.j2 delete mode 100644 roles/nut_exporter/README.md delete mode 100644 roles/nut_exporter/defaults/main.yml delete mode 100644 roles/nut_exporter/handlers/main.yml delete mode 100644 roles/nut_exporter/meta/main.yml delete mode 100644 roles/nut_exporter/tasks/main.yml delete mode 100644 roles/nut_exporter/templates/override.conf.j2 delete mode 100644 roles/nut_exporter/vars/archlinux.yml diff --git a/roles/nut/README.md b/roles/nut/README.md index ce0cb49..ca0ca9b 100644 --- a/roles/nut/README.md +++ b/roles/nut/README.md @@ -82,7 +82,28 @@ journalctl -u nut-monitor -u nut-server -u 'nut-driver@*' -f - udev rules shipped by the `nut` package grant USB device access to the `nut` group only. -## Companion role +## Prometheus exporter -See [`nut_exporter`](../nut_exporter/README.md) to expose Prometheus metrics -based on the same upsd instance. +Optionally exposes UPS metrics for Prometheus via `prometheus-nut-exporter` +(AUR, Arch-only). Enable it: + +```yaml +nut_prometheus_exporter_enabled: true +``` + +The exporter reuses `nut_monitor_password`, binds to +`{{ nut_prometheus_exporter_listen_address }}` (default `127.0.0.1:9199`) and +serves UPS metrics on `/ups_metrics`. Pair it with a Prometheus scrape job: + +```yaml +prometheus_scrape_configs: + - job_name: 'nut' + metrics_path: /ups_metrics + static_configs: + - targets: ['127.0.0.1:9199'] +``` + +```bash +systemctl status prometheus-nut-exporter +curl -s 'http://127.0.0.1:9199/ups_metrics' | head +``` diff --git a/roles/nut/defaults/main.yml b/roles/nut/defaults/main.yml index a3d73d6..7813273 100644 --- a/roles/nut/defaults/main.yml +++ b/roles/nut/defaults/main.yml @@ -5,7 +5,7 @@ # UPS definition # -------------- # Logical name of the UPS as referenced everywhere (ups.conf section, upsmon -# MONITOR line, nut_exporter ?ups= query parameter). +# MONITOR line, exporter ?ups= query parameter). nut_ups_name: eaton # Human-readable description (shown in upsc output). @@ -33,7 +33,7 @@ nut_ups_maxretry: 3 nut_upsd_listen: - { addr: "127.0.0.1", port: 3493 } -# Local monitor user used by upsmon and nut_exporter. Password must be set. +# Local monitor user used by upsmon and the Prometheus exporter. Password must be set. nut_monitor_user: monitor # nut_monitor_password: "" # Intentionally undefined - role will fail if not set @@ -66,3 +66,10 @@ nut_notify_script_path: /usr/local/bin/ups-notify # ONLINE notification can report total outage duration. Lives under /run so it # is cleared on reboot (stale outage state is meaningless after a reboot). nut_outage_state_file: /run/ups-notify-outage + +# Prometheus exporter (optional) +# ------------------------------ +nut_prometheus_exporter_enabled: false +nut_prometheus_exporter_listen_address: "127.0.0.1:9199" +nut_prometheus_exporter_nut_server: "127.0.0.1:3493" +nut_prometheus_exporter_nut_user: "{{ nut_monitor_user }}" diff --git a/roles/nut/handlers/main.yml b/roles/nut/handlers/main.yml index 7944b2d..e1707ee 100644 --- a/roles/nut/handlers/main.yml +++ b/roles/nut/handlers/main.yml @@ -13,3 +13,12 @@ ansible.builtin.systemd: name: nut-monitor.service state: restarted + +- name: Reload systemd + ansible.builtin.systemd: + daemon_reload: true + +- name: Restart NUT exporter + ansible.builtin.systemd: + name: "{{ nut_prometheus_exporter_service }}" + state: restarted diff --git a/roles/nut/tasks/exporter.yml b/roles/nut/tasks/exporter.yml new file mode 100644 index 0000000..bd98050 --- /dev/null +++ b/roles/nut/tasks/exporter.yml @@ -0,0 +1,32 @@ +--- +# Prometheus NUT exporter (optional, gated by nut_prometheus_exporter_enabled). +# Installs prometheus-nut-exporter (AUR) and points it at the local upsd via +# the unit's EnvironmentFile (/etc/conf.d/prometheus-nut-exporter). + +- name: Install prometheus-nut-exporter (AUR via paru) + ansible.builtin.command: "paru -S --noconfirm --needed {{ nut_prometheus_exporter_package }}" + register: _nut_prometheus_exporter_install + changed_when: "'there is nothing to do' not in _nut_prometheus_exporter_install.stdout | lower" + when: ansible_facts['os_family'] == 'Archlinux' + +- name: Remove legacy exporter systemd override + ansible.builtin.file: + path: "/etc/systemd/system/{{ nut_prometheus_exporter_service }}.service.d" + state: absent + notify: Reload systemd + +- name: Deploy exporter environment file + ansible.builtin.template: + src: nut-exporter-env.conf.j2 + dest: "{{ nut_prometheus_exporter_env_file }}" + owner: root + group: root + mode: "0600" + notify: Restart NUT exporter + +- name: Enable and start exporter + ansible.builtin.systemd: + name: "{{ nut_prometheus_exporter_service }}" + enabled: true + state: started + daemon_reload: true diff --git a/roles/nut/tasks/main.yml b/roles/nut/tasks/main.yml index 189c535..723df0d 100644 --- a/roles/nut/tasks/main.yml +++ b/roles/nut/tasks/main.yml @@ -95,3 +95,7 @@ enabled: true state: started loop: "{{ nut_services }}" + +- name: Deploy Prometheus NUT exporter + ansible.builtin.include_tasks: exporter.yml + when: nut_prometheus_exporter_enabled | bool diff --git a/roles/nut/templates/nut-exporter-env.conf.j2 b/roles/nut/templates/nut-exporter-env.conf.j2 new file mode 100644 index 0000000..8ff6d9c --- /dev/null +++ b/roles/nut/templates/nut-exporter-env.conf.j2 @@ -0,0 +1,5 @@ +# Managed by Ansible - DO NOT EDIT MANUALLY +# EnvironmentFile for prometheus-nut-exporter (read by the systemd unit via +# $NUT_EXPORTER_ARGS). The password has no flag, so it is passed as an env var. +NUT_EXPORTER_ARGS="--web.listen-address={{ nut_prometheus_exporter_listen_address }} --nut.server={{ nut_prometheus_exporter_nut_server.split(':')[0] }} --nut.serverport={{ nut_prometheus_exporter_nut_server.split(':')[1] }} --nut.username={{ nut_prometheus_exporter_nut_user }}" +NUT_EXPORTER_PASSWORD="{{ nut_monitor_password }}" diff --git a/roles/nut/vars/archlinux.yml b/roles/nut/vars/archlinux.yml index e79960a..6df6375 100644 --- a/roles/nut/vars/archlinux.yml +++ b/roles/nut/vars/archlinux.yml @@ -7,3 +7,8 @@ nut_services: - nut-driver-enumerator.service - nut-server.service - nut-monitor.service + +# Prometheus exporter (AUR) +nut_prometheus_exporter_package: prometheus-nut-exporter +nut_prometheus_exporter_service: prometheus-nut-exporter +nut_prometheus_exporter_env_file: /etc/conf.d/prometheus-nut-exporter diff --git a/roles/nut_exporter/README.md b/roles/nut_exporter/README.md deleted file mode 100644 index 8ed5fb6..0000000 --- a/roles/nut_exporter/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# nut_exporter — Prometheus exporter for NUT - -Scrapes a local `upsd` and exposes UPS metrics for Prometheus. - -## Supported distributions - -- Arch Linux (AUR package `prometheus-nut-exporter`, installed via `paru`) - -Debian/Ubuntu is not packaged upstream — add it on demand. - -## Configuration - -See [defaults/main.yml](defaults/main.yml). - -Required: - -```yaml -nut_exporter_nut_password: "" -``` - -Optional: - -```yaml -nut_exporter_listen_address: "127.0.0.1:9199" -nut_exporter_nut_server: "127.0.0.1:3493" -nut_exporter_nut_user: monitor -``` - -## Pairing with Prometheus - -Typical scrape config (target uses the multi-target pattern: the exporter -queries a remote upsd specified in the URL parameters): - -```yaml -prometheus_scrape_configs: - - job_name: 'nut' - metrics_path: /nut - static_configs: - - targets: ['eaton@localhost'] # ups@host syntax - relabel_configs: - - source_labels: [__address__] - target_label: __param_target - - source_labels: [__param_target] - target_label: instance - - target_label: __address__ - replacement: 127.0.0.1:9199 -``` - -## Operations - -```bash -systemctl status prometheus-nut-exporter -curl -s 'http://127.0.0.1:9199/nut?target=localhost&ups=eaton' | head -journalctl -u prometheus-nut-exporter -f -``` - -## Dependencies - -Requires the [`nut`](../nut/README.md) role (or any other running upsd) on the -same host. diff --git a/roles/nut_exporter/defaults/main.yml b/roles/nut_exporter/defaults/main.yml deleted file mode 100644 index 5a2b4ac..0000000 --- a/roles/nut_exporter/defaults/main.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- -# Prometheus NUT exporter configuration - -# Address the exporter listens on. -nut_exporter_listen_address: "127.0.0.1:9199" - -# upsd server to connect to (kept local — exporter sits next to upsd). -nut_exporter_nut_server: "127.0.0.1:3493" - -# Credentials used to log into upsd. These should match the upsd user defined -# by the nut role (nut_monitor_user / nut_monitor_password). -nut_exporter_nut_user: "{{ nut_monitor_user | default('monitor') }}" -# nut_exporter_nut_password: "" # Inherits nut_monitor_password by default diff --git a/roles/nut_exporter/handlers/main.yml b/roles/nut_exporter/handlers/main.yml deleted file mode 100644 index e577c03..0000000 --- a/roles/nut_exporter/handlers/main.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -- name: Reload systemd - ansible.builtin.systemd: - daemon_reload: true - -- name: Restart nut_exporter - ansible.builtin.systemd: - name: "{{ nut_exporter_service }}" - state: restarted diff --git a/roles/nut_exporter/meta/main.yml b/roles/nut_exporter/meta/main.yml deleted file mode 100644 index 23d65c7..0000000 --- a/roles/nut_exporter/meta/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -dependencies: [] diff --git a/roles/nut_exporter/tasks/main.yml b/roles/nut_exporter/tasks/main.yml deleted file mode 100644 index 3ce4a18..0000000 --- a/roles/nut_exporter/tasks/main.yml +++ /dev/null @@ -1,46 +0,0 @@ ---- -- name: Validate required configuration - ansible.builtin.assert: - that: - - nut_exporter_nut_password is defined - - nut_exporter_nut_password | length >= 12 - fail_msg: | - nut_exporter_nut_password (>=12 chars) is required. - Usually set to the same value as nut_monitor_password. - -- name: Load OS-specific variables - ansible.builtin.include_vars: "{{ item }}" - with_first_found: - - "{{ ansible_facts['os_family'] }}.yml" - -- name: Install prometheus-nut-exporter (AUR via paru) - ansible.builtin.command: "paru -S --noconfirm --needed {{ nut_exporter_package }}" - register: nut_exporter_install - changed_when: "'there is nothing to do' not in nut_exporter_install.stdout | lower" - when: ansible_facts['os_family'] == 'Archlinux' - -- name: Ensure systemd override directory exists - ansible.builtin.file: - path: "{{ nut_exporter_override_dir }}" - state: directory - owner: root - group: root - mode: "0755" - -- name: Deploy systemd override (listen address + upsd credentials) - ansible.builtin.template: - src: override.conf.j2 - dest: "{{ nut_exporter_override_dir }}/override.conf" - owner: root - group: root - mode: "0640" - notify: - - Reload systemd - - Restart nut_exporter - -- name: Enable and start nut_exporter - ansible.builtin.systemd: - name: "{{ nut_exporter_service }}" - enabled: true - state: started - daemon_reload: true diff --git a/roles/nut_exporter/templates/override.conf.j2 b/roles/nut_exporter/templates/override.conf.j2 deleted file mode 100644 index abca590..0000000 --- a/roles/nut_exporter/templates/override.conf.j2 +++ /dev/null @@ -1,10 +0,0 @@ -# Managed by Ansible - DO NOT EDIT MANUALLY -# Override for prometheus-nut-exporter to inject listen address and upsd -# credentials. The exporter reads NUT_EXPORTER_* env vars at startup. - -[Service] -Environment="HTTP_LISTEN_ADDRESS={{ nut_exporter_listen_address }}" -Environment="NUT_EXPORTER_SERVER={{ nut_exporter_nut_server.split(':')[0] }}" -Environment="NUT_EXPORTER_PORT={{ nut_exporter_nut_server.split(':')[1] }}" -Environment="NUT_EXPORTER_USERNAME={{ nut_exporter_nut_user }}" -Environment="NUT_EXPORTER_PASSWORD={{ nut_exporter_nut_password }}" diff --git a/roles/nut_exporter/vars/archlinux.yml b/roles/nut_exporter/vars/archlinux.yml deleted file mode 100644 index 0b66674..0000000 --- a/roles/nut_exporter/vars/archlinux.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -nut_exporter_package: prometheus-nut-exporter -nut_exporter_service: prometheus-nut-exporter.service -nut_exporter_user: nut-exporter -nut_exporter_group: nut-exporter -nut_exporter_override_dir: /etc/systemd/system/prometheus-nut-exporter.service.d