diff --git a/local/lib/python3/cmk/base/plugins/agent_based/nextcloud_database.py b/local/lib/python3/cmk/base/plugins/agent_based/nextcloud_database.py new file mode 100644 index 0000000..723dab9 --- /dev/null +++ b/local/lib/python3/cmk/base/plugins/agent_based/nextcloud_database.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +from pprint import pprint +from .agent_based_api.v1 import register, render, Service, Result, State, Metric + +def getStateUpper(levels, value): + warn, crit = levels + if value >= crit: + return State.CRIT + if value >= warn: + return State.WARN + return State.OK + +def getStateLower(levels, value): + warn, crit = levels + if value < crit: + return State.CRIT + if value < warn: + return State.WARN + return State.OK + +def discover_nextcloud_database(section): + yield(Service()) + +def check_nextcloud_database(params, section): + for key in section: + if key == "database": + opcache_hit_rate = section[key]["opcache_hit_rate"] + size = section[key]["size"] + type = section[key]["type"] + version = section[key]["version"] + 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) + # create graph for database size + yield Metric("nc_database_size", size) + state = getStateLower(levels, opcache_hit_rate) + summary = f"Cache hit rate: {render.percent(opcache_hit_rate)}" + details = f"\nDatabase type: {type}\nDatabase version: {version}\nDatabase size: {render.bytes(size)}" + yield(Result(state=state, summary=summary, details=details)) + +def parse_nextcloud_database_section(string_table): + parsed_data = { + "database" : {} + } + params_list = [ + "NC_Database_Type", + "NC_Database_Version", + "NC_Database_Size", + "NC_OPCache_Hit_Rate" + ] + for line in string_table: + if line[0] in params_list: + param = line[0] + value = line[1] + if param == "NC_Database_Type": + parsed_data["database"]["type"] = value + elif param == "NC_Database_Version": + parsed_data["database"]["version"] = value + elif param == "NC_Database_Size": + parsed_data["database"]["size"] = int(value) + elif param == "NC_OPCache_Hit_Rate": + parsed_data["database"]["opcache_hit_rate"] = float(value) + return parsed_data + +register.agent_section( + name="nextcloud_database", + parse_function=parse_nextcloud_database_section, +) + +register.check_plugin( + name="nextcloud_database", + service_name="Nextcloud Database", + discovery_function=discover_nextcloud_database, + check_function=check_nextcloud_database, + check_default_parameters={ + "levels_database_opcache_hit_rate": (99.7, 99.1), + }, + check_ruleset_name="nextcloud_database", +) diff --git a/local/lib/python3/cmk/base/plugins/agent_based/nextcloud_info.py b/local/lib/python3/cmk/base/plugins/agent_based/nextcloud_info.py new file mode 100644 index 0000000..aaf879e --- /dev/null +++ b/local/lib/python3/cmk/base/plugins/agent_based/nextcloud_info.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +from pprint import pprint +from .agent_based_api.v1 import register, render, Service, Result, State, Metric + +def getStateUpper(levels, value): + warn, crit = levels + if value >= crit: + return State.CRIT + if value >= warn: + return State.WARN + return State.OK + +def getStateLower(levels, value): + warn, crit = levels + if value < crit: + return State.CRIT + if value < warn: + return State.WARN + return State.OK + +def discover_nextcloud_info(section): + yield(Service()) + +def check_nextcloud_info(params, section): + for key in section: + if key == "nextcloud": + status = section[key]["status"] + version = section[key]["version"] + num_files = section[key]["number_files"] + num_shares = section[key]["number_shares"] + # create graph for number of files and shares + yield(Metric("nc_num_files", num_files)) + yield(Metric("nc_num_shares", num_shares)) + summary = f"Status is {status}" + details = f"Version is {version}\nNumber of files: {num_files}\nNumber of shares: {num_shares}\n" + if status == "ok": + state = State.OK + else: + state = State.CRIT + yield Result(state=state, summary=summary, details=details) + elif key == "users": + num_users = section[key]["number"] + num_active_last1hour = section[key]["active_last1hour"] + num_active_last1day = section[key]["active_last1day"] + num_active_last5min = section[key]["active_last5min"] + # create graphs for number of users + yield Metric("nc_num_users", num_users) + yield Metric("nc_active_users_last_1hour", num_active_last1hour) + yield Metric("nc_active_users_last_1day", num_active_last1day) + yield Metric("nc_active_users_last_5min", num_active_last5min) + notice = f"Number of users: {num_users}\n\nActive users last 5 min: {num_active_last5min}\nActive user since last hour: {num_active_last1hour}\nActive users since last day: {num_active_last1day}" + yield(Result(state=State.OK, notice=notice)) + elif key == "storages": + num_storages = section[key]["number"] + num_storages_home = section[key]["number_home"] + num_storages_local = section[key]["number_local"] + num_storages_other = section[key]["number_other"] + # create graphs for number of storages + yield Metric("nc_num_storages", num_storages) + yield Metric("nc_num_storages_home", num_storages_home) + yield Metric("nc_num_storages_local", num_storages_local) + yield Metric("nc_num_storages_other", num_storages_other) + notice = f"Number of storages: {num_storages}\nNumber of home/local/other storages: {num_storages_home}/{num_storages_local}/{num_storages_other}" + yield(Result(state=State.OK, notice=notice)) + elif key == "apps": + num_apps_installed = section[key]["installed"] + num_apps_with_updates_available = section[key]["with_updates_available"] + # create graphs for number of apps + 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 = getStateUpper(levels, num_apps_with_updates_available) + notice = f"Number of installed apps: {num_apps_installed}\nNumber of apps with updates available: {num_apps_with_updates_available}" + yield(Result(state=state, notice=notice)) + +def parse_nextcloud_info_section(string_table): + parsed_data = { + "nextcloud" : {}, + "storages" : {}, + "apps" : {}, + "users" : {}, + } + params_list = [ + "NC_Version", + "NC_Status", + "NC_Num_Users", + "NC_Num_Files", + "NC_Num_Shares", + "NC_Num_Storages", + "NC_Num_Storages_Home", + "NC_Num_Storages_Local", + "NC_Num_Storages_Other", + "NC_Num_Apps_Installed", + "NC_Num_Apps_Updates_Available", + "NC_Active_Users_Last_5Min", + "NC_Active_Users_Last_1Hour", + "NC_Active_Users_Last_1Day" + ] + for line in string_table: + if line[0] in params_list: + param = line[0] + value = line[1] + if param == "NC_Version": + parsed_data["nextcloud"]["version"] = value + elif param == "NC_Status": + parsed_data["nextcloud"]["status"] = value + elif param == "NC_Num_Files": + parsed_data["nextcloud"]["number_files"] = int(value) + elif param == "NC_Num_Shares": + parsed_data["nextcloud"]["number_shares"] = int(value) + elif param == "NC_Num_Storages": + parsed_data["storages"]["number"] = int(value) + elif param == "NC_Num_Storages_Home": + parsed_data["storages"]["number_home"] = int(value) + elif param == "NC_Num_Storages_Local": + parsed_data["storages"]["number_local"] = int(value) + elif param == "NC_Num_Storages_Other": + parsed_data["storages"]["number_other"] = int(value) + elif param == "NC_Num_Apps_Installed": + parsed_data["apps"]["installed"] = int(value) + elif param == "NC_Num_Apps_Updates_Available": + parsed_data["apps"]["with_updates_available"] = int(value) + elif param == "NC_Num_Users": + parsed_data["users"]["number"] = int(value) + elif param == "NC_Active_Users_Last_5Min": + parsed_data["users"]["active_last5min"] = int(value) + elif param == "NC_Active_Users_Last_1Hour": + parsed_data["users"]["active_last1hour"] = int(value) + elif param == "NC_Active_Users_Last_1Day": + parsed_data["users"]["active_last1day"] = int(value) + return parsed_data + +register.agent_section( + name="nextcloud_info", + parse_function=parse_nextcloud_info_section, +) + +register.check_plugin( + name="nextcloud_info", + service_name="Nextcloud Info", + discovery_function=discover_nextcloud_info, + check_function=check_nextcloud_info, + check_default_parameters={ + "levels_apps_with_updates_available": (1, 2), + }, + check_ruleset_name="nextcloud_info", +) diff --git a/local/lib/python3/cmk/base/plugins/agent_based/nextcloud_users.py b/local/lib/python3/cmk/base/plugins/agent_based/nextcloud_users.py new file mode 100644 index 0000000..d943e5c --- /dev/null +++ b/local/lib/python3/cmk/base/plugins/agent_based/nextcloud_users.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +from pprint import pprint +from time import time +from .agent_based_api.v1 import register, render, Service, Result, State, Metric + +def getStateUpper(levels, value): + warn, crit = levels + if value >= crit: + return State.CRIT + if value >= warn: + return State.WARN + return State.OK + +def getStateLower(levels, value): + warn, crit = levels + if value < crit: + return State.CRIT + if value < warn: + return State.WARN + return State.OK + +def discover_nextcloud_users(section): + for key in section: + yield(Service(item = key)) + +def check_nextcloud_users(item, params, section): + userid = item + quota_used_percent = section[item][0] + quota_used_bytes = section[item][1] + quota_total_bytes = section[item][2] + display_name = section[item][3] + last_login_human = section[item][4] + last_login_since = section[item][5] + free_space = quota_total_bytes - quota_used_bytes + print(free_space) + levels_quota_used = params["levels_users_quota_used"] + levels_free_space = params["levels_users_free_space"] + if last_login_human == "never": + quota_used_percent = 0 + details = f"User ID is '{userid}', Last login: {last_login_human}" + summary = f"Used quota of '{display_name}' can't be calculated yet (never logged in)" + 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 = getStateLower((warn, crit), 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_quota_used", quota_used_percent, levels=levels_quota_used) + state = getStateUpper(levels_quota_used, quota_used_percent) + yield(Result(state=state, summary=summary, details=details)) + +def parse_nextcloud_users_section(string_table): + # Raw output from check: + # userid;displayname;lastLogin;quota_free;quota_quota;quota_relative;quota_total;quota_used + # str;str;int(milli seconds since epoch);int(bytes);int(bytes);float(percent);int(bytes);int(bytes) + parsed_data = {} + for line in string_table: + userid = line[0] + display_name = line[1] + last_login = int(line[2])/1000 + if last_login == 0: + # user never logged in + last_login_human = "never" + last_login_since = "never" + else: + # user logged in at least once + curr_time = int(time()) + login_diff = curr_time - last_login + last_login_human = render.datetime(last_login) + last_login_since = render.timespan(login_diff) + quota_relative = float(line[5]) + quota_total = float(line[6]) + quota_used = float(line[7]) + parsed_data[f"{userid}"] = [quota_relative, quota_used, quota_total, display_name, last_login_human, last_login_since] + return parsed_data + +register.agent_section( + name = "nextcloud_users", + parse_function = parse_nextcloud_users_section, +) + +register.check_plugin( + name = "nextcloud_users", + service_name = "Nextcloud User %s", + discovery_function = discover_nextcloud_users, + check_function = check_nextcloud_users, + check_default_parameters = { + "levels_users_quota_used": (65.0, 85.00), + "levels_users_free_space": (256.0, 128.0) + }, + check_ruleset_name="nextcloud_users", +) \ No newline at end of file diff --git a/local/share/check_mk/agents/special/agent_nextcloud b/local/share/check_mk/agents/special/agent_nextcloud new file mode 100755 index 0000000..24f707a --- /dev/null +++ b/local/share/check_mk/agents/special/agent_nextcloud @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 + +import getopt +import sys +import requests +import urllib3 +import json +import os +from pprint import pprint +from requests.structures import CaseInsensitiveDict +from requests.auth import HTTPBasicAuth + +def showUsage(): + sys.stderr.write("""CheckMK Nextcloud Special Agent + +USAGE: agent_nextcloud_info -u [username] -p [password] + OR + agent_nextcloud_info -u [username] -t [token] + agent_nextcloud_info -h + +OPTIONS: + -H, --hostname Hostname (FQDN or IP) of Nextcloud server + -u, --username Username + -p, --password Password + -P, --port Port + -f, --folder Subfolder if not installed in web root + -t, --token Token (recommended) + --no-https Disable HTTPS, use HTTP (not recommended!) + --no-cert-check Disable TLS certificate check (not recommended!) + -h, --help Show this help message and exit +""") + +DEBUG = False + +nc_api_endpoint = "ocs/v2.php/apps/serverinfo/api/v1/info?format=json" +nc_api_endpoint_all_users = "ocs/v1.php/cloud/users?format=json" +nc_api_endpoint_user = "ocs/v1.php/cloud/users" + +opt_hostname = "" +opt_username = "" +opt_password = "" +opt_port = "" +opt_folder = "" +opt_token = "0" +opt_no_https = False +opt_no_cert_check = False + +short_options = 'hH:u:p:P:f:t:' +long_options = [ + 'hostname=', 'username=', 'password=', 'port=', 'token=', 'folder=', 'no-https', 'no-cert-check', 'help' +] + +def getOptions(): + global opt_hostname + global opt_username + global opt_password + global opt_port + global opt_folder + global opt_token + global opt_no_https + global opt_no_cert_check + + opts, args = getopt.getopt(sys.argv[1:], short_options, long_options) + for opt, arg in opts: + if opt in ['-H', '--hostname']: + opt_hostname = arg + if opt in ['-u', '--username']: + opt_username = arg + elif opt in ['-p', '--password']: + opt_password = arg + elif opt in ['-P', '--port']: + opt_port = arg + elif opt in ['-f', '--folder']: + opt_folder = arg + elif opt in ['-t', '--token']: + opt_token = arg + elif opt in ['--no-https']: + opt_no_https = True + elif opt in ['--no-cert-check']: + opt_no_cert_check = True + elif opt in ['-h', '--help']: + showUsage() + sys.exit(0) + +def showOptions(): + print(f"Hostname: {opt_hostname}") + print(f"Username: {opt_username}") + #print(f"Password: {opt_password}") + print(f"Port: {opt_port}") + print(f"Folder: {opt_folder}") + #print(f"Token: {opt_token}") + print(f"No HTTPS: {opt_no_https}") + print(f"No TLS Check: {opt_no_cert_check}") + if DEBUG: + home_path = os.getenv("HOME") + tmp_path = f"{home_path}/tmp" + help_file = f"{tmp_path}/nextcloud_{opt_hostname}_debug.txt" + with open(help_file, "a") as file: + file.write(f"Hostname: {opt_hostname}, Port: {opt_port}, No HTTPS: {opt_no_https} \n") + +def createUrl(endpoint, hostname, protocol, port, folder): + if folder == "": + url = f"{protocol}://{hostname}:{port}/{endpoint}" + else: + url = f"{protocol}://{hostname}:{port}/{folder}/{endpoint}" + if DEBUG: + home_path = os.getenv("HOME") + tmp_path = f"{home_path}/tmp" + help_file = f"{tmp_path}/nextcloud_{hostname}_debug.txt" + with open(help_file, "a") as file: + file.write(f"Data URL: {url}\n") + return url + +def createUrlUser(user, endpoint, hostname, protocol, port, folder): + if folder == "": + url = f"{protocol}://{hostname}:{port}/{endpoint}/{user}?format=json" + else: + url = f"{protocol}://{hostname}:{port}/{folder}/{endpoint}/{user}?format=json" + if DEBUG: + home_path = os.getenv("HOME") + tmp_path = f"{home_path}/tmp" + help_file = f"{tmp_path}/nextcloud_{hostname}_debug.txt" + with open(help_file, "a") as file: + file.write(f"User URL: {url}\n") + return url + +def getData(url, verify): + headers = CaseInsensitiveDict() + headers["Accept"] = "application/json" + if (opt_token == '0'): + # authenticate with username and password + pwd = opt_password + else: + # authenticate with token + pwd = opt_token + response = requests.get(url, auth=HTTPBasicAuth(opt_username, pwd), headers=headers, verify=verify) + status = response.status_code + if (status == 200): + jsdata = response.text + data = json.loads(jsdata) # returns a dictionary + return data + elif (status == 503): + # this code is reported when maintenance mode is on + sys.stderr.write(f"Request response code is {response.status_code} with URL {url}, maybe maintenance mode is on?\n") + sys.exit(1) + else: + sys.stderr.write(f"Request response code is {response.status_code} with URL {url}\n") + sys.exit(1) + +def getDataAllUsers(url, verify): + headers = CaseInsensitiveDict() + headers["Accept"] = "application/json" + headers["OCS-APIRequest"] = "true" + if (opt_token == '0'): + # authenticate with username and password + pwd = opt_password + else: + # authenticate with token + pwd = opt_token + response = requests.get(url, auth=HTTPBasicAuth(opt_username, pwd), headers=headers, verify=verify) + status = response.status_code + if (status == 200): + jsdata = response.text + data = json.loads(jsdata) # returns a dictionary + return data + else: + sys.stderr.write(f"Request response code is {response.status_code} with URL {url}\n") + sys.exit(1) + +def getDataUser(url, verify): + #print(url) + headers = CaseInsensitiveDict() + headers["Accept"] = "application/json" + headers["OCS-APIRequest"] = "true" + if (opt_token == '0'): + # authenticate with username and password + pwd = opt_password + else: + # authenticate with token + pwd = opt_token + response = requests.get(url, auth=HTTPBasicAuth(opt_username, pwd), headers=headers, verify=verify) + status = response.status_code + if (status == 200): + jsdata = response.text + data = json.loads(jsdata) # returns a dictionary + return data + else: + sys.stderr.write(f"Request response code is {response.status_code} with URL {url}\n") + sys.exit(1) + +def doCmkOutput(data): + print("<<>>") + print(f"NC_Version;{data['ocs']['data']['nextcloud']['system']['version']}") + print(f"NC_Status;{data['ocs']['meta']['status']}") + print(f"NC_Num_Users;{data['ocs']['data']['nextcloud']['storage']['num_users']}") + print(f"NC_Num_Files;{data['ocs']['data']['nextcloud']['storage']['num_files']}") + print(f"NC_Num_Shares;{data['ocs']['data']['nextcloud']['shares']['num_shares']}") + print(f"NC_Num_Storages;{data['ocs']['data']['nextcloud']['storage']['num_storages']}") + print(f"NC_Num_Storages_Home;{data['ocs']['data']['nextcloud']['storage']['num_storages_home']}") + print(f"NC_Num_Storages_Local;{data['ocs']['data']['nextcloud']['storage']['num_storages_local']}") + print(f"NC_Num_Storages_Other;{data['ocs']['data']['nextcloud']['storage']['num_storages_other']}") + print(f"NC_Num_Apps_Installed;{data['ocs']['data']['nextcloud']['system']['apps']['num_installed']}") + print(f"NC_Num_Apps_Updates_Available;{data['ocs']['data']['nextcloud']['system']['apps']['num_updates_available']}") + print(f"NC_Active_Users_Last_5Min;{data['ocs']['data']['activeUsers']['last5minutes']}") + print(f"NC_Active_Users_Last_1Hour;{data['ocs']['data']['activeUsers']['last1hour']}") + print(f"NC_Active_Users_Last_1Day;{data['ocs']['data']['activeUsers']['last24hours']}") + + print("<<>>") + print(f"NC_Database_Type;{data['ocs']['data']['server']['database']['type']}") + print(f"NC_Database_Version;{data['ocs']['data']['server']['database']['version']}") + print(f"NC_Database_Size;{data['ocs']['data']['server']['database']['size']}") + print(f"NC_OPCache_Hit_Rate;{data['ocs']['data']['server']['php']['opcache']['opcache_statistics']['opcache_hit_rate']}") + +def doCmkOutputAllUsers(data, verify, hostname, protocol, port, folder): + print("<<>>") + for user in data['ocs']['data']['users']: + nc_url = createUrlUser(user, nc_api_endpoint_user, hostname, protocol, port, folder) + user_data = getDataUser(nc_url, verify) + userid = user_data['ocs']['data']['id'] + displayname = user_data['ocs']['data']['displayname'] + lastlogin = int(user_data['ocs']['data']['lastLogin']) + if lastlogin == 0: + # user has never logged in + quota_free = -1 + quota_quota = -1 + quota_relative = -1 + quota_total = -1 + quota_used = -1 + else: + quota_free = user_data['ocs']['data']['quota']['free'] + # quota_quota == -3 --> unlimited + quota_quota = user_data['ocs']['data']['quota']['quota'] + # quota_relative = used * 100 / (free + used) + quota_relative = user_data['ocs']['data']['quota']['relative'] + quota_total = user_data['ocs']['data']['quota']['total'] + quota_used = user_data['ocs']['data']['quota']['used'] + print(f"{userid};{displayname};{lastlogin};{quota_free};{quota_quota};{quota_relative};{quota_total};{quota_used}") + +def main(): + getOptions() + if DEBUG: + showOptions() + if (opt_hostname == ""): + sys.stderr.write(f"No hostname given.\n") + showUsage() + sys.exit(1) + if (opt_no_cert_check): + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + verify = False + else: + verify = True + if (opt_port == ""): + if (opt_no_https): + protocol = "http" + port = "80" + else: + protocol = "https" + port = "443" + else: + if (opt_no_https): + protocol = "http" + else: + protocol = "https" + port = opt_port + nc_url = createUrl(nc_api_endpoint, opt_hostname, protocol, port, opt_folder) + nc_data = getData(nc_url, verify) + doCmkOutput(nc_data) + nc_url = createUrl(nc_api_endpoint_all_users, opt_hostname, protocol, port, opt_folder) + nc_data = getDataAllUsers(nc_url, verify) + doCmkOutputAllUsers(nc_data, verify, opt_hostname, protocol, port, opt_folder) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/local/share/check_mk/checkman/nextcloud_database b/local/share/check_mk/checkman/nextcloud_database new file mode 100644 index 0000000..bd78a04 --- /dev/null +++ b/local/share/check_mk/checkman/nextcloud_database @@ -0,0 +1,16 @@ +title: Nextcloud: Database information +agents: linux +catalog: unsorted +license: GPL +distribution: check_mk +description: + Works with Nextcloud version 25.0.3 (use at your own risk with lower versions). + Tested only with mysql/mariab as underlying database. + You may use a username/password or username/token combination to get access to the Nextcloud API. + You can create this token within the personal settings of an administrative user in Nextcloud. + Got to security settings and create a new app password, use this for the token. + The user must not be secured with 2FA. + Shows several information about a Nextcloud database (type/version/size). + The check will raise WARN/CRIT if Database OP cache hit rate is below the configurable levels. +inventory: + one service is created (with several details) diff --git a/local/share/check_mk/checkman/nextcloud_info b/local/share/check_mk/checkman/nextcloud_info new file mode 100644 index 0000000..a744f79 --- /dev/null +++ b/local/share/check_mk/checkman/nextcloud_info @@ -0,0 +1,18 @@ +title: Nextcloud: Various information +agents: linux +catalog: unsorted +license: GPL +distribution: check_mk +description: + Works with Nextcloud version 25.0.3 (use at your own risk with lower versions). + Tested only with mysql/mariab as underlying database. + You may use a username/password or username/token combination to get access to the Nextcloud API. + You can create this token within the personal settings of an administrative user in Nextcloud. + Got to security settings and create a new app password, use this for the token. + The user must not be secured with 2FA. + Shows several information about a Nextcloud instance (e.g. number of files/storages/users. + The check will raise CRIT if the Nextcloud instance is not in "ok" state. + The check will raise WARN/CRIT if Database OP cache hit rate is below the configurable levels. + The check will raise WARN/CRIT if the number of installed apps with available updates is above the configurable levels. +inventory: + one service is created (with several details) diff --git a/local/share/check_mk/checkman/nextcloud_users b/local/share/check_mk/checkman/nextcloud_users new file mode 100644 index 0000000..b5060fb --- /dev/null +++ b/local/share/check_mk/checkman/nextcloud_users @@ -0,0 +1,19 @@ +title: Nextcloud: Quota and Storage Usage of Users +agents: linux +catalog: unsorted +license: GPL +distribution: check_mk +description: + Works with Nextcloud version 25.0.3 (use at your own risk with lower versions). + You may use a username/password or username/token combination to get access to the Nextcloud API. + You can create this token within the personal settings of an administrative user in Nextcloud. + Got to security settings and create a new app password, use this for the token. + The user must not be secured with 2FA. + Shows the usage of storage quota used by each user in percent of the maximum allowed quota. + Shows the remaining free space for each user based on his/her max quota and used space. + The check will raise WARN/CRIT if quota usage is above the configurable levels. + The check will raise WARN/CRIT if free space is below the configurable levels. +item: + userid +inventory: + one service is created for each user diff --git a/local/share/check_mk/checks/agent_nextcloud b/local/share/check_mk/checks/agent_nextcloud new file mode 100644 index 0000000..ad50f75 --- /dev/null +++ b/local/share/check_mk/checks/agent_nextcloud @@ -0,0 +1,14 @@ +def agent_nextcloud_arguments(params, hostname, ipaddress): + return [ + "--hostname", params["hostname"], + "--username", params["username"], + "--password", params["password"], + "--token", params["token"], + "--port", params["port"], + "--folder", params["folder"], + "--no-https", params["no_https"], + "--no-cert-check", params["no_cert_check"], + ipaddress, + ] + +special_agent_info['nextcloud'] = agent_nextcloud_arguments \ No newline at end of file diff --git a/local/share/check_mk/web/plugins/metrics/nextcloud_metrics.py b/local/share/check_mk/web/plugins/metrics/nextcloud_metrics.py new file mode 100644 index 0000000..d288db7 --- /dev/null +++ b/local/share/check_mk/web/plugins/metrics/nextcloud_metrics.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +from cmk.gui.i18n import _ +from cmk.gui.plugins.metrics import ( + metric_info, + graph_info, +) + +metric_info["nc_num_users"] = { + "title": _("Number of Users"), + "unit": "count", + "color": "44/a", +} + +metric_info["nc_num_shares"] = { + "title": _("Number of Shares"), + "unit": "count", + "color": "44/b", +} + +metric_info["nc_num_storages"] = { + "title": _("Number of Storages"), + "unit": "count", + "color": "42/a", +} + +metric_info["nc_num_storages_home"] = { + "title": _("Number of Home Storages"), + "unit": "count", + "color": "44/a", +} + +metric_info["nc_num_storages_local"] = { + "title": _("Number of Local Storages"), + "unit": "count", + "color": "44/b", +} + +metric_info["nc_num_storages_other"] = { + "title": _("Number of Other Storages"), + "unit": "count", + "color": "42/a", +} + +metric_info["nc_num_files"] = { + "title": _("Number of Files"), + "unit": "count", + "color": "42/a", +} + +metric_info["nc_num_apps_installed"] = { + "title": _("Number of installed Apps"), + "unit": "count", + "color": "43/a", +} + +metric_info["nc_apps_with_updates_available"] = { + "title": _("Number of installed Apps with Updates Available"), + "unit": "count", + "color": "24/a", +} + +metric_info["nc_active_users_last_5min"] = { + "title": _("Number of Active Users in the last 5 minutes"), + "unit": "count", + "color": "24/a", +} + +metric_info["nc_active_users_last_1hour"] = { + "title": _("Number of Active Users in the last 1 hour"), + "unit": "count", + "color": "41/a", +} + +metric_info["nc_active_users_last_1day"] = { + "title": _("Number of Active Users in the last 1 day"), + "unit": "count", + "color": "42/a", +} + +metric_info["nc_users_free_space"] = { + "title": _("Free Space"), + "unit": "bytes", + "color": "22/a", +} + +metric_info["nc_database_size"] = { + "title": _("Database Size"), + "unit": "bytes", + "color": "24/a", +} + +metric_info["nc_database_opcache_hit_rate"] = { + "title": _("Database OP Cache Hit Rate"), + "unit": "%", + "color": "24/a", +} + +metric_info["nc_users_quota_used"] = { + "title": _("Quota used"), + "unit": "%", + "color": "16/a", +} + +graph_info["number_of_users_shares_storages_combined"] = { + "title": _("Number of Users, Shares and Storages"), + "metrics": [ + ("nc_num_users", "area"), + ("nc_num_shares", "stack"), + ("nc_num_storages", "stack"), + ], +} + +graph_info["number_of_storage_types_combined"] = { + "title": _("Number of Storage Types"), + "metrics": [ + ("nc_num_storages_home", "area"), + ("nc_num_storages_local", "stack"), + ("nc_num_storages_other", "stack"), + ], +} + +graph_info["number_of_active_users_combined"] = { + "title": _("Number of Active Users"), + "metrics": [ + ("nc_active_users_last_5min", "area"), + ("nc_active_users_last_1hour", "stack"), + ("nc_active_users_last_1day", "stack"), + ], +} \ No newline at end of file diff --git a/local/share/check_mk/web/plugins/perfometer/nextcloud_perfometers.py b/local/share/check_mk/web/plugins/perfometer/nextcloud_perfometers.py new file mode 100644 index 0000000..a513286 --- /dev/null +++ b/local/share/check_mk/web/plugins/perfometer/nextcloud_perfometers.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +from cmk.gui.plugins.metrics import perfometer_info + +perfometer_info.append({ + "type": "stacked", + "perfometers": [ + { + "type": "linear", + "segments": ["nc_database_opcache_hit_rate"], + "total": 100.0, + }, + ], +}) + +perfometer_info.append({ + "type": "stacked", + "perfometers": [ + { + "type": "linear", + "segments": ["nc_database_size"], + }, + ], +}) + +perfometer_info.append({ + "type": "stacked", + "perfometers": [ + { + "type": "linear", + "segments": ["nc_users_quota_used"], + "total": 100.0, + }, + ], +}) \ No newline at end of file diff --git a/local/share/check_mk/web/plugins/wato/nextcloud_database_rules.py b/local/share/check_mk/web/plugins/wato/nextcloud_database_rules.py new file mode 100644 index 0000000..9d4615c --- /dev/null +++ b/local/share/check_mk/web/plugins/wato/nextcloud_database_rules.py @@ -0,0 +1,43 @@ +from cmk.gui.i18n import _ +from cmk.gui.plugins.wato import ( + CheckParameterRulespecWithoutItem, + rulespec_registry, + RulespecGroupCheckParametersApplications +) +from cmk.gui.valuespec import ( + Dictionary, + ListChoice, + Tuple, + Percentage, + Integer, + Float, +) + +def _parameter_spec_nextcloud_database(): + return Dictionary( + elements=[ + ("levels_database_opcache_hit_rate", Tuple( + title=_("Nextcloud levels for database opcache hit rate"), + elements=[ + Percentage( + title=_("Warning below"), + default_value=99.7, + ), + Percentage( + title=_("Critical below"), + default_value=99.1, + ) + ], + )), + ], + ) + +rulespec_registry.register( + CheckParameterRulespecWithoutItem( + check_group_name="nextcloud_database", + group=RulespecGroupCheckParametersApplications, + match_type="dict", + parameter_valuespec=_parameter_spec_nextcloud_database, + title=lambda: _("Nextcloud Database"), + ) +) \ No newline at end of file diff --git a/local/share/check_mk/web/plugins/wato/nextcloud_info_rules.py b/local/share/check_mk/web/plugins/wato/nextcloud_info_rules.py new file mode 100644 index 0000000..19cb5c9 --- /dev/null +++ b/local/share/check_mk/web/plugins/wato/nextcloud_info_rules.py @@ -0,0 +1,43 @@ +from cmk.gui.i18n import _ +from cmk.gui.plugins.wato import ( + CheckParameterRulespecWithoutItem, + rulespec_registry, + RulespecGroupCheckParametersApplications +) +from cmk.gui.valuespec import ( + Dictionary, + ListChoice, + Tuple, + Percentage, + Integer, + Float, +) + +def _parameter_spec_nextcloud_info(): + return Dictionary( + elements=[ + ("levels_apps_with_updates_available", Tuple( + title=_("Nextcloud number of installed apps with updates available"), + elements=[ + Integer( + title=_("Warning at"), + default_value=1, + ), + Integer( + title=_("Critical at"), + default_value=2, + ) + ], + )), + ], + ) + +rulespec_registry.register( + CheckParameterRulespecWithoutItem( + check_group_name="nextcloud_info", + group=RulespecGroupCheckParametersApplications, + match_type="dict", + parameter_valuespec=_parameter_spec_nextcloud_info, + title=lambda: _("Nextcloud Info"), + ) +) \ No newline at end of file diff --git a/local/share/check_mk/web/plugins/wato/nextcloud_params.py b/local/share/check_mk/web/plugins/wato/nextcloud_params.py new file mode 100644 index 0000000..9992a3b --- /dev/null +++ b/local/share/check_mk/web/plugins/wato/nextcloud_params.py @@ -0,0 +1,59 @@ +from cmk.gui.i18n import _ +from cmk.gui.plugins.wato import ( + CheckParameterRulespecWithItem, + rulespec_registry, + RulespecGroupCheckParametersOperatingSystem, +) +from cmk.gui.valuespec import ( + Dictionary, + ListChoice, + Checkbox, + TextAscii, + Password, +) + +from cmk.gui.plugins.wato import ( + HostRulespec, +) +from cmk.gui.plugins.wato.datasource_programs import RulespecGroupDatasourceProgramsCustom +import cmk.gui.watolib as watolib + +def _valuespec_special_agent_nextcloud(): + return Dictionary( + title=_("Nextcloud Server Information"), + help = _("Checking Nextcloud servers via API"), + elements=[ + ("hostname", TextAscii(title=_("Hostname"), + allow_empty=False, + help=_("Hostname of Nextcloud server (bare FQDN or IP, IP not tested), mandatory"))), + ("username", TextAscii(title=_("Username"), + allow_empty=False, + help=_("Username with administrative rights, mandatory"))), + ("password", Password(title=_("Password"), + allow_empty=True, + help=_("Specify password OR token, not both, token recommended"))), + ("token", Password(title=_("Token"), + allow_empty=True, + help=_("Specify password OR token, not both, token recommended"))), + ("port", TextAscii(title=_("Port"), + allow_empty=True, + help=_("Specify port if not listening to HTTPS, optional"))), + ("folder", TextAscii(title=_("Folder"), + allow_empty=True, + help=_("Specify subfolder if your Nextcloud instance is not installed in the web root, no trailing/leading slashes, optional"))), + ("no_https", Checkbox(title=_("Disable HTTPS"), + help=_("Activate to disable encryption (not recommended), optional"))), + ("no_cert_check", Checkbox(title=_("Disable certificate validation"), + help=_("Activate to disable certificate validation (not recommended), optional"))), + ], + optional_keys=[], + ) + +rulespec_registry.register( + HostRulespec( + factory_default=watolib.Rulespec.FACTORY_DEFAULT_UNUSED, + group=RulespecGroupDatasourceProgramsCustom, + name="special_agents:nextcloud", + valuespec=_valuespec_special_agent_nextcloud, + ) +) \ No newline at end of file diff --git a/local/share/check_mk/web/plugins/wato/nextcloud_users_rules.py b/local/share/check_mk/web/plugins/wato/nextcloud_users_rules.py new file mode 100644 index 0000000..a9877d2 --- /dev/null +++ b/local/share/check_mk/web/plugins/wato/nextcloud_users_rules.py @@ -0,0 +1,66 @@ +from cmk.gui.i18n import _ +from cmk.gui.plugins.wato import ( + CheckParameterRulespecWithItem, + rulespec_registry, + RulespecGroupCheckParametersApplications +) +from cmk.gui.valuespec import ( + Dictionary, + ListChoice, + Tuple, + Percentage, + Integer, + Float, +) + +def _item_spec_nextcloud_users(): + return TextAscii( + title=_("User ID") + ) + +def _parameter_spec_nextcloud_users(): + return Dictionary( + elements=[ + ("levels_users_quota_used", Tuple( + title=_("Nextcloud levels for quota usage of users"), + elements=[ + Percentage( + title=_("Warning at"), + default_value=65.0, + unit="%", + ), + Percentage( + title=_("Critical at"), + default_value=85.0, + unit="%", + ) + ], + )), + ("levels_users_free_space", Tuple( + title=_("Nextcloud levels for free disk space of users"), + elements=[ + Float( + title=_("Warning below"), + default_value=256.0, + unit="MBytes", + ), + Float( + title=_("Critical below"), + default_value=128.0, + unit="MBytes", + ) + ], + )), + ], + ) + +rulespec_registry.register( + CheckParameterRulespecWithItem( + check_group_name="nextcloud_users", + group=RulespecGroupCheckParametersApplications, + match_type="dict", + item_spec=_item_spec_nextcloud_users, + parameter_valuespec=_parameter_spec_nextcloud_users, + title=lambda: _("Nextcloud Users"), + ) +) \ No newline at end of file