diff --git a/.README.md.swp b/.README.md.swp new file mode 100644 index 0000000..6ca08da Binary files /dev/null and b/.README.md.swp differ diff --git a/README.md b/README.md index 5f6cf72..9a0b09b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,16 @@ # Nextcloud CheckMK Special Agent Monitors various aspects of Nextcloud instances like state, quota and disk usage of all users, number of apps with available updates, database php opcache hit rate and so on. Gives additional information regarding versions of Nextcloud, database, number of storages and active users etc. -Tested only with Nextcloud versions 29/30 + +Tested only with Nextcloud versions 29/30/31/32 + Tested only with MariaDB as database backend + The whole plugin is now migrated to the new plugin API version, introduced with CheckMK 2.3 + Feel free to report other working environments. + Upgrade from older MKPs (before 3.1.1): If you upgrade from a already installed MKP version before 3.1.0, you have to re-create your rules for the "Nextcloud Server Information" (reason: some parameter changes combined with the migration to the new plugin API). @@ -18,7 +23,7 @@ Hint: It is always a good idea to create screenshots from all other Nextcloud ru 5. Re-create your rules with the previously saved information from steps 1 and 2 6. Apply your changes -Hint: You have to create an app password now for accessing your Nextcloud instance. For this to accomplish login to your Nextcloud server with an administrative user account. Go to "Personal Settings|Security" and take note of the section "Devices & Sessions". Create the app password via clicking the button "create new app password". You may use this password explicitly within the rule or store it first in the password safe of CheckMK. +Hint: You have to create an app password now for accessing your Nextcloud instance. For this to accomplish login to your Nextcloud server with an administrative user account. Go to "Personal Settings|Security" and take note of the section "Devices & Sessions". Create the app password via clicking the button "create new app password". You may use this password explicitly within the rule or store it first in the password safe of CheckMK (recommended). General installation instructions: @@ -40,7 +45,8 @@ Version History: -- |Date|Version|Changes| |----|-------|-------| -|2025/03/30|3.1.1|Migration to the new plugin API version finished, (hopefully) ready for CheckMK version 2.4 +|2026/03/09|3.2.0|Fixed bugs when: Users are deleted, quotas not set, rules with no levels are active| +|2025/03/30|3.1.1|Migration to the new plugin API version finished, (hopefully) ready for CheckMK version 2.4| |2023/01/13|2.5.2|Repackaged only to set the minimum required version back to 2.1.0p1, due to serveral user requests| |2023/01/12|2.5.1|Added versions for apps with available updates| |2023/01/12|2.4.1|Removed Parameter "token", switched to parameter "app password" only| diff --git a/mkp/Nextcloud-3.2.0.mkp b/mkp/Nextcloud-3.2.0.mkp new file mode 100644 index 0000000..ccc2e3d Binary files /dev/null and b/mkp/Nextcloud-3.2.0.mkp differ diff --git a/nextcloud/agent_based/nextcloud_database.py b/nextcloud/agent_based/nextcloud_database.py index db64233..a8bbcc0 100644 --- a/nextcloud/agent_based/nextcloud_database.py +++ b/nextcloud/agent_based/nextcloud_database.py @@ -2,6 +2,7 @@ # pylint: disable=missing-module-docstring, unused-argument, missing-function-docstring # pylint: disable=line-too-long +from typing import Final from collections.abc import Mapping from typing import NotRequired, TypedDict @@ -18,6 +19,8 @@ from cmk.agent_based.v2 import ( DiscoveryResult, ) +NO_LEVELS: Final[str] = "no_levels" + class _ItemData(TypedDict): dbtype: NotRequired[str] @@ -49,14 +52,18 @@ def check_nextcloud_database(params, section) -> CheckResult: size = section[key]["size"] dbtype = section[key]["dbtype"] version = section[key]["version"] - _level_type, levels = params["levels_database_opcache_hit_rate"] + level_type, levels = params["levels_database_opcache_hit_rate"] # create graph for opcache hit rate - yield Metric( - "nc_database_opcache_hit_rate", opcache_hit_rate, levels=levels - ) + if level_type == NO_LEVELS: + yield Metric("nc_database_opcache_hit_rate", opcache_hit_rate) + state = State.OK + else: + yield Metric( + "nc_database_opcache_hit_rate", opcache_hit_rate, levels=levels + ) + state = get_state_lower(levels, opcache_hit_rate) # create graph for database size yield Metric("nc_database_size", size) - state = get_state_lower(levels, opcache_hit_rate) summary = f"PHP OPCache hit rate: {render.percent(opcache_hit_rate)}" if opcache_hit_rate == 0: state = State.UNKNOWN diff --git a/nextcloud/agent_based/nextcloud_info.py b/nextcloud/agent_based/nextcloud_info.py index 6cd8621..b4e445a 100644 --- a/nextcloud/agent_based/nextcloud_info.py +++ b/nextcloud/agent_based/nextcloud_info.py @@ -3,6 +3,7 @@ # pylint: disable=line-too-long, too-many-branches, too-many-locals, too-many-statements from datetime import datetime +from typing import Final from cmk.agent_based.v2 import ( AgentSection, @@ -17,6 +18,7 @@ from cmk.agent_based.v2 import ( DiscoveryResult, ) +NO_LEVELS: Final[str] = "no_levels" def get_state_upper(levels, value): warn, crit = levels @@ -43,8 +45,8 @@ def discover_nextcloud_info(section) -> DiscoveryResult: def check_nextcloud_info(params, section) -> CheckResult: for key in section: if key == "nextcloud": - _level_type, levels_free_space = params["levels_free_space"] - _level_type, levels_number_of_files = params["levels_number_of_files"] + level_type_free_space, levels_free_space = params["levels_free_space"] + level_type_number_of_files, levels_number_of_files = params["levels_number_of_files"] # update infos are available only from Nextcloud version 28 onwards try: last_update = section[key]["last_update"] @@ -89,19 +91,28 @@ def check_nextcloud_info(params, section) -> CheckResult: # Create result for free space on disk # Levels for free space are given in GBytes, we have to adjust this here - warn, crit = levels_free_space - warn = warn * 1024 * 1024 * 1024 - crit = crit * 1024 * 1024 * 1024 - state = get_state_lower((warn, crit), free_space) - # create graph for free space on disk - yield Metric("nc_free_space", free_space, levels=(warn, crit)) + if level_type_free_space != NO_LEVELS: + warn, crit = levels_free_space + warn = warn * 1024 * 1024 * 1024 + crit = crit * 1024 * 1024 * 1024 + state = get_state_lower((warn, crit), free_space) + # create graph for free space on disk + yield Metric("nc_free_space", free_space, levels=(warn, crit)) + else: + # a rule with no levels set is active, assume OK state + state = State.OK + yield Metric("nc_free_space", free_space) notice = f"Remaining free space on disk: {render.bytes(free_space)}" if state != State.OK: yield Result(state=state, notice=notice) # Create result for number of files - warn, crit = levels_number_of_files - state = get_state_upper((warn, crit), num_files) + if level_type_number_of_files != NO_LEVELS: + warn, crit = levels_number_of_files + state = get_state_upper((warn, crit), num_files) + else: + # a rule with no levels set is active, assume OK state + state = State.OK notice = f"Number of files: {num_files}" if state != State.OK: yield Result(state=state, notice=notice) @@ -139,14 +150,22 @@ def check_nextcloud_info(params, section) -> CheckResult: else: app_versions = "" # create graphs for number of apps - _level_type, levels = params["levels_apps_with_updates_available"] + level_type, levels = params["levels_apps_with_updates_available"] yield Metric("nc_num_apps_installed", num_apps_installed) - yield Metric( - "nc_apps_with_updates_available", - num_apps_with_updates_available, - levels=levels, - ) - state = get_state_upper(levels, num_apps_with_updates_available) + if level_type != NO_LEVELS: + yield Metric( + "nc_apps_with_updates_available", + num_apps_with_updates_available, + levels=levels, + ) + state = get_state_upper(levels, num_apps_with_updates_available) + else: + # a rule with no levels set is active, assume OK state + yield Metric( + "nc_apps_with_updates_available", + num_apps_with_updates_available, + ) + state = State.OK if app_versions == "": notice = f"Number of installed apps: {num_apps_installed}\nNumber of apps with updates available: {num_apps_with_updates_available}" else: diff --git a/nextcloud/agent_based/nextcloud_users.py b/nextcloud/agent_based/nextcloud_users.py index c28093b..9658c88 100644 --- a/nextcloud/agent_based/nextcloud_users.py +++ b/nextcloud/agent_based/nextcloud_users.py @@ -3,7 +3,7 @@ # pylint: disable=line-too-long, too-many-locals from time import time -from pprint import pprint +from typing import Final from cmk.agent_based.v2 import ( AgentSection, @@ -18,6 +18,7 @@ from cmk.agent_based.v2 import ( DiscoveryResult, ) +NO_LEVELS: Final[str] = "no_levels" def get_state_upper(levels, value): warn, crit = levels @@ -43,6 +44,10 @@ def discover_nextcloud_users(section) -> DiscoveryResult: def check_nextcloud_users(item, params, section) -> CheckResult: + attr = section.get(item) + if not attr: + yield Result(state=State.UNKNOWN, summary="User not found anymore, maybe it has been deleted.") + return userid = item quota_used_percent = section[item][0] quota_used_bytes = section[item][1] @@ -56,8 +61,8 @@ def check_nextcloud_users(item, params, section) -> CheckResult: quota_is_set = False else: quota_is_set = True - _level_type, levels_quota_used = params["levels_users_quota_used"] - _level_type, levels_free_space = params["levels_users_free_space"] + level_type_quota_used, levels_quota_used = params["levels_users_quota_used"] + level_type_free_space, levels_free_space = params["levels_users_free_space"] if (last_login_human == "never") or (not quota_is_set): details = f"User ID is '{userid}', Last login: {last_login_human}" summary = ( @@ -65,19 +70,29 @@ def check_nextcloud_users(item, params, section) -> CheckResult: ) else: # Levels are given in MBytes, we have to adjust this here - warn, crit = levels_free_space - warn = warn * 1024 * 1024 - crit = crit * 1024 * 1024 - state = get_state_lower((warn, crit), free_space) + if level_type_free_space != NO_LEVELS: + warn, crit = levels_free_space + warn = warn * 1024 * 1024 + crit = crit * 1024 * 1024 + state = get_state_lower((warn, crit), free_space) + yield Metric("nc_users_free_space", free_space, levels=(warn, crit)) + else: + # a rule with no levels set is active, so assume OK state + state = State.OK + yield Metric("nc_users_free_space", free_space) + details = f"User ID is '{userid}'\nLast login: {last_login_human} ({last_login_since} ago)\nFree space: {render.bytes(free_space)}" summary = f"Used quota of '{display_name}' is {render.percent(quota_used_percent)}, {render.bytes(quota_used_bytes)}/{render.bytes(quota_total_bytes)} used" notice = f"Remaining free space: {render.bytes(free_space)}" - yield Metric("nc_users_free_space", free_space, levels=(warn, crit)) if state != State.OK: yield Result(state=state, notice=notice) - yield Metric("nc_users_free_space", free_space, levels=(warn, crit)) - yield Metric("nc_users_quota_used", quota_used_percent, levels=levels_quota_used) - state = get_state_upper(levels_quota_used, quota_used_percent) + if level_type_quota_used != NO_LEVELS: + yield Metric("nc_users_quota_used", quota_used_percent, levels=levels_quota_used) + state = get_state_upper(levels_quota_used, quota_used_percent) + else: + # a rule with no levels set is active, so assume OK state + yield Metric("nc_users_quota_used", quota_used_percent) + state = State.OK yield Result(state=state, summary=summary, details=details) diff --git a/nextcloud/checkman/nextcloud_database b/nextcloud/checkman/nextcloud_database index 5c61c6d..82d1710 100644 --- a/nextcloud/checkman/nextcloud_database +++ b/nextcloud/checkman/nextcloud_database @@ -4,7 +4,7 @@ catalog: unsorted license: GPL distribution: check_mk description: - Works with Nextcloud version 25/26/27/28/29/30 (use at your own risk with lower versions). + Works with Nextcloud version 25/26/27/28/29/30/31/32 (use at your own risk with lower versions). Tested only with mariab as underlying database. You have to use a username/app password combination to get access to the Nextcloud API. You can create this app password within the personal settings of an administrative user in Nextcloud. diff --git a/nextcloud/checkman/nextcloud_info b/nextcloud/checkman/nextcloud_info index 3e00f08..377f3c1 100644 --- a/nextcloud/checkman/nextcloud_info +++ b/nextcloud/checkman/nextcloud_info @@ -4,9 +4,9 @@ catalog: unsorted license: GPL distribution: check_mk description: - Works with Nextcloud version 25/26/27/28/29/30 (use at your own risk with lower versions). + Works with Nextcloud version 25/26/27/28/29/30/31/32 (use at your own risk with lower versions). Tested only with mariab as underlying database. - You have to use a username/app password combination to get access to the Nextcloud API. + You have to use a username/app password combination to get access to the Nextcloud API (including user info). You can create this app password within the personal settings of an administrative user in Nextcloud. Got to security settings and create a new app password. The user must not be secured with 2FA. diff --git a/nextcloud/checkman/nextcloud_users b/nextcloud/checkman/nextcloud_users index be2c4ad..7508335 100644 --- a/nextcloud/checkman/nextcloud_users +++ b/nextcloud/checkman/nextcloud_users @@ -4,7 +4,7 @@ catalog: unsorted license: GPL distribution: check_mk description: - Works with Nextcloud version 25/26/27/28/29/30 (use at your own risk with lower versions). + Works with Nextcloud version 25/26/27/28/29/30/31/32 (use at your own risk with lower versions). Tested only with mariab as underlying database. You have to use a username/app password combination to get access to the Nextcloud API. You can create this app password within the personal settings of an administrative user in Nextcloud.