Hi,
here some small helpers β¦ feel free to add
mutch better ![]()
thats the reason β¦ share to get better ![]()
Hi,
here some small helpers β¦ feel free to add
mutch better ![]()
thats the reason β¦ share to get better ![]()
Tool to read a password from the password store. Needs the password ID as argument.
#!/usr/bin/env python3
from cmk.utils import password_store
from sys import argv
pw = password_store.lookup(
pw_id=argv[1],
pw_file=password_store.password_store_path()
)
print(pw)
pycache files should be recompiled by checkmk, but we just got confirmation on a bug that this doesnβt always happen, hopefully the clean-reload gets simpler again once this is fixed.
however, I would think for discovery over UI and other things to pick up changes, a βomd restart automation-helperβ and βomd restart ui-job-schedulerβ would be good to be safe?
This is my restart script for development:
#!/bin/bash
for s in automation-helper ui-job-scheduler apache; do
omd restart $s
done
cmk -R
Scripte for automatic creation of checkman basic-pages according the check_name or inventory_name
#!/bin/bash
# -----------------------------------------
# Arguments: SITE + PLUGINNAME
# -----------------------------------------
if [[ -z "$1" || -z "$2" ]]; then
echo "Usage: $0 <site> <pluginname>"
echo "Example: $0 test xiq"
exit 1
fi
SITE="$1"
PLUGIN="$2"
BASE="/opt/omd/sites/${SITE}/local/lib/python3/cmk_addons/plugins/${PLUGIN}"
SRC="${BASE}/agent_based"
DST="${BASE}/checkman"
echo "Site: $SITE"
echo "Pluginname: $PLUGIN"
echo "Source: $SRC"
echo "Destination: $DST"
echo
mkdir -p "$DST"
# -----------------------------------------
# Function: extract plugin name
# Supports:
# check_plugin_<name> = CheckPlugin(
# CheckPlugin(name="<name>"
# inventory_plugin_<name> = InventoryPlugin(
# InventoryPlugin(name="<name>"
# -----------------------------------------
extract_plugin_name() {
local file="$1"
local NAME=""
# Variant A1: variable starts with check_plugin_
NAME=$(grep -Po 'check_plugin_\K[A-Za-z0-9_]+' "$file")
if [[ -n "$NAME" ]]; then
echo "$NAME"
return
fi
# Variant A2: variable starts with inventory_plugin_
NAME=$(grep -Po 'inventory_plugin_\K[A-Za-z0-9_]+' "$file")
if [[ -n "$NAME" ]]; then
echo "$NAME"
return
fi
# Variant B1: name="xyz" inside CheckPlugin(...)
NAME=$(grep -Po 'CheckPlugin\s*\(\s*name\s*=\s*"\K[^"]+' "$file")
if [[ -n "$NAME" ]]; then
echo "$NAME"
return
fi
# Variant B2: name="xyz" inside InventoryPlugin(...)
NAME=$(grep -Po 'InventoryPlugin\s*\(\s*name\s*=\s*"\K[^"]+' "$file")
if [[ -n "$NAME" ]]; then
echo "$NAME"
return
fi
echo ""
}
# -----------------------------------------
# MAIN LOOP
# -----------------------------------------
for file in "$SRC"/*.py; do
filename=$(basename "$file")
echo "Processing: $filename"
CHECK_NAME=$(extract_plugin_name "$file")
if [[ -z "$CHECK_NAME" ]]; then
echo " -> No CheckPlugin / InventoryPlugin found. Skipping."
continue
fi
target="${DST}/${CHECK_NAME}"
if [[ -e "$target" ]]; then
echo " -> [SKIP] ${CHECK_NAME} (already exists)"
else
echo " -> [NEW] Creating ${target}"
cat > "$target" <<EOF
title: ${CHECK_NAME}
agents: piggyback
catalog: custom/${PLUGIN}
license: GPLv2
distribution: check_mk
description:
This man page was auto-generated for plugin '${CHECK_NAME}'.
Please update this description manually.
discovery:
One service or inventory entry is created.
EOF
# Clean unsafe characters (Windows CP1252, BOM, HTML entities)
sed -i 's/\r$//' "$target"
sed -i 's/\xEF\xBB\xBF//g' "$target"
sed -i 's/\x96/-/g' "$target"
sed -i 's/\x97/-/g' "$target"
sed -i 's/\x91/'\''/g' "$target"
sed -i 's/\x92/'\''/g' "$target"
sed -i 's/\x93/"/g' "$target"
sed -i 's/\x94/"/g' "$target"
sed -i 's/>/>/g' "$target"
sed -i 's/</</g' "$target"
fi
done
echo
echo "Done."
`
OMD[test]:~$ ./create_checkman_from_checks.sh test xiq
Site: test
Pluginname: xiq
Source: /opt/omd/sites/test/local/lib/python3/cmk_addons/plugins/xiq/agent_based
Destination: /opt/omd/sites/test/local/lib/python3/cmk_addons/plugins/xiq/checkman
Processing: check_ap_clients.py
-> [SKIP] xiq_ap_clients (already exists)
Processing: check_ap_uptime.py
-> [SKIP] xiq_ap_uptime (already exists)
Processing: check_neighbors.py
-> [SKIP] xiq_ap_neighbors (already exists)
Processing: check_radios.py
-> [SKIP] xiq_radios (already exists)
Processing: check_rate_limits.py
-> [SKIP] xiq_rate_limits (already exists)
Processing: check_ssid_clients.py
-> [SKIP] xiq_ssid_clients (already exists)
Processing: check_status.py
-> [SKIP] xiq_ap_status (already exists)
Processing: check_summary.py
-> [SKIP] xiq_summary (already exists)
Processing: common.py
-> No CheckPlugin / InventoryPlugin found. Skipping.
Processing: inventory_active_clients.py
-> [NEW] Creating /opt/omd/sites/test/local/lib/python3/cmk_addons/plugins/xiq/checkman/xiq_active_clients
Processing: inventory_devices.py
-> [NEW] Creating /opt/omd/sites/test/local/lib/python3/cmk_addons/plugins/xiq/checkman/xiq_devices
Processing: inventory_neighbors.py
-> [NEW] Creating /opt/omd/sites/test/local/lib/python3/cmk_addons/plugins/xiq/checkman/xiq_neighbors
Processing: inventory_radios_bssids.py
-> [NEW] Creating /opt/omd/sites/test/local/lib/python3/cmk_addons/plugins/xiq/checkman/xiq_ap_radios_plugin
xiq_ap_bssids_plugin
Processing: sections.py
-> No CheckPlugin / InventoryPlugin found. Skipping.
`
and another script to create the hole plugin Folder structure
call
OMD[test]:~$ ./create_plugin_structure.sh xiq
`
#!/bin/bash
# ----------------------------------------------------------
# Create Checkmk extension folder structure for a plugin
# Usage: ./create_plugin_structure.sh <pluginname>
# ----------------------------------------------------------
if [[ -z "$1" ]]; then
echo "Usage: $0 <pluginname>"
echo "Example: $0 xiq"
exit 1
fi
PLUGIN="$1"
# Base folder inside the site environment
BASE_DIR="$HOME/local/lib/python3/cmk_addons/plugins/${PLUGIN}"
echo "Creating plugin folder structure for plugin: ${PLUGIN}"
echo "Base directory: ${BASE_DIR}"
echo
# List all required subdirectories according to Checkmk plugin API layout:
DIRS=(
"agent_based"
"checkman"
"graphing"
"inventory"
"libexec"
"rulesets"
"server_side_calls"
"web"
)
# Create directories
for d in "${DIRS[@]}"; do
TARGET="${BASE_DIR}/${d}"
if [[ -d "$TARGET" ]]; then
echo "[SKIP] $TARGET (already exists)"
else
echo "[NEW] $TARGET"
mkdir -p "$TARGET"
fi
done
echo
echo "Plugin directory structure created successfully."
sample output:
Creating plugin folder structure for plugin: xiq
Base directory: /omd/sites/test/local/lib/python3/cmk_addons/plugins/xiq
[NEW] .../agent_based
[NEW] .../checkman
[NEW] .../graphing
[NEW] .../inventory
[NEW] .../libexec
[NEW] .../rulesets
[NEW] .../server_side_calls
[NEW] .../web
and now the advanced version for special_agent_skeleton creator:
Call & output
OMD[test]:~$ ./special_agent_skeleton.sh myapi <-- YOUR_PLUGIN_NAME
=========================================
Special Agent Skeleton Created!
=========================================
Plugin: myapi
Location: /omd/sites/test/local/lib/python3/cmk_addons/plugins/myapi
Files created:
β /omd/sites/test/local/lib/python3/cmk_addons/plugins/myapi/libexec/agent_myapi
β /omd/sites/test/local/lib/python3/cmk_addons/plugins/myapi/rulesets/special_agent.py
β /omd/sites/test/local/lib/python3/cmk_addons/plugins/myapi/server_side_calls/special_agent.py
β /omd/sites/test/local/lib/python3/cmk_addons/plugins/myapi/README.md
Features:
β Username/Password authentication
β Encrypted password storage
β SSL verification (configurable)
β HTTP requests with error handling
Next steps:
1. Edit libexec/agent_myapi - customize API calls
2. Run: cmk -R
3. Configure in Setup β Agents β VM, Cloud, Container
4. Test with: cmk -D <HOSTNAME>
#!/usr/bin/env bash
# =============================================================================
# Create Checkmk 2.4 Special Agent Skeleton (with Authentication Template)
# =============================================================================
# Usage:
# ./mk_special_agent_skeleton.sh <plugin_family> [<site_user>] [--with-checks]
#
# Example:
# ./mk_special_agent_skeleton.sh myapi # minimal (agent + ruleset + call)
# ./mk_special_agent_skeleton.sh myapi test --with-checks # includes check plugin
#
# Template based on logincheck agent showing:
# - Username/Password handling (encrypted storage)
# - HTTP requests with requests library
# - SSL verification option
# - Proper error handling
# - Response parsing
#
# References:
# - https://docs.checkmk.com/latest/en/devel_intro.html (Introduction to developing extensions)
# - https://docs.checkmk.com/latest/en/devel_special_agents.html (Special agents development)
# - https://docs.checkmk.com/latest/en/devel_check_plugins.html (Agent-based check plug-ins)
# - https://docs.checkmk.com/latest/en/devel_check_plugins_rulesets.html (Rulesets API)
# - Help > Developer resources > Plug-in API references (in Checkmk GUI)
# =============================================================================
set -euo pipefail
# ----- Parse arguments --------------------------------------------------------
PLUGIN_FAMILY=""
SITE_USER=""
WITH_CHECKS=false
while [[ $# -gt 0 ]]; do
case "$1" in
--with-checks) WITH_CHECKS=true; shift ;;
*)
if [[ -z "$PLUGIN_FAMILY" ]]; then
PLUGIN_FAMILY="$1"
elif [[ -z "$SITE_USER" ]]; then
SITE_USER="$1"
else
echo "Unknown argument: $1" >&2
exit 1
fi
shift
;;
esac
done
if [[ -z "$PLUGIN_FAMILY" ]]; then
echo "Usage: $0 <plugin_family> [<site_user>] [--with-checks]"
exit 1
fi
# Validate plugin family name
if [[ ! "$PLUGIN_FAMILY" =~ ^[a-zA-Z0-9_][-a-zA-Z0-9_]*$ ]]; then
echo "Error: <plugin_family> must match [a-zA-Z0-9_][-_a-zA-Z0-9_]*"
exit 2
fi
# Base directory
BASE_DIR="${HOME}/local/lib/python3/cmk_addons/plugins/${PLUGIN_FAMILY}"
AGENT_BASENAME="${PLUGIN_FAMILY}"
AGENT_EXEC="agent_${AGENT_BASENAME}"
# Create directories
mkdir -p "${BASE_DIR}/libexec"
mkdir -p "${BASE_DIR}/rulesets"
mkdir -p "${BASE_DIR}/server_side_calls"
if [[ "$WITH_CHECKS" == true ]]; then
mkdir -p "${BASE_DIR}/agent_based"
fi
# =============================================================================
# SPECIAL AGENT EXECUTABLE (with authentication)
# =============================================================================
AGENT_PATH="${BASE_DIR}/libexec/${AGENT_EXEC}"
if [[ ! -e "${AGENT_PATH}" ]]; then
cat > "${AGENT_PATH}" <<'EOF'
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Special Agent for PLUGIN_FAMILY
Connects to API with authentication and retrieves monitoring data.
"""
import argparse
import json
import sys
from typing import Any
import requests
def fetch_api_data(
url: str,
username: str,
password: str,
insecure: bool = False,
timeout: int = 60,
) -> dict[str, Any]:
"""
Fetch data from API endpoint with authentication.
Args:
url: API endpoint URL
username: Authentication username
password: Authentication password
insecure: Disable SSL verification
timeout: Request timeout in seconds
Returns:
Dictionary with API response data
Raises:
requests.RequestException: On connection/request errors
"""
session = requests.Session()
if insecure:
session.verify = False
# Suppress InsecureRequestWarning
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Configure authentication - adjust based on your API
# Option 1: Basic Auth
# session.auth = (username, password)
# Option 2: Token-based (login first)
payload = {
"username": username,
"password": password,
}
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
# Login request (adjust endpoint and method as needed)
try:
login_resp = session.post(
url,
json=payload,
headers=headers,
timeout=timeout,
allow_redirects=True,
)
login_resp.raise_for_status()
except requests.RequestException as e:
raise requests.RequestException(f"Login failed: {e}")
# Extract token or session cookie (adjust based on your API)
# token = login_resp.json().get("token")
# session.headers.update({"Authorization": f"Bearer {token}"})
# Fetch actual data (replace with your API endpoints)
# Example: Get system status
try:
status_resp = session.get(
f"{url}/status", # Adjust endpoint
timeout=timeout,
)
status_resp.raise_for_status()
data = status_resp.json()
except requests.RequestException as e:
raise requests.RequestException(f"Data fetch failed: {e}")
return data
def output_section(name: str, data: Any) -> None:
"""
Output a Checkmk agent section.
For JSON data, output on a single line so the check plugin
can parse it correctly with itertools + json.loads().
See: https://docs.checkmk.com/latest/en/devel_special_agents.html
"""
print(f"<<<{name}>>>")
if isinstance(data, (dict, list)):
# Output JSON on a single line
print(json.dumps(data))
elif isinstance(data, str):
print(data)
else:
print(str(data))
def main() -> int:
parser = argparse.ArgumentParser(
description="Checkmk special agent: PLUGIN_FAMILY"
)
parser.add_argument("--url", required=True, help="API endpoint URL")
parser.add_argument("--username", required=True, help="Authentication username")
parser.add_argument("--password", required=True, help="Authentication password")
parser.add_argument("--insecure", action="store_true", help="Disable SSL verification")
parser.add_argument("--timeout", type=int, default=60, help="Request timeout (seconds)")
args = parser.parse_args()
try:
# Fetch data from API
data = fetch_api_data(
url=args.url,
username=args.username,
password=args.password,
insecure=args.insecure,
timeout=args.timeout,
)
# Output section(s) - adjust based on your data structure
# Example: Main status section
output_section("PLUGIN_FAMILY_status", {
"status": data.get("status", "UNKNOWN"),
"code": data.get("code", "N/A"),
"message": data.get("message", "No message"),
})
# Example: Additional data section (if available)
if "items" in data:
output_section("PLUGIN_FAMILY_items", data["items"])
return 0
except requests.RequestException as e:
# Output error section so check plugin can handle it
output_section("PLUGIN_FAMILY_status", {
"status": "FAILED",
"code": "EXCEPTION",
"message": str(e)[:500], # Limit error message length
})
return 0 # Return 0 to allow check plugin to process error
except Exception as e:
print(f"ERROR: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
EOF
sed -i "s/PLUGIN_FAMILY/${PLUGIN_FAMILY}/g" "${AGENT_PATH}"
chmod +x "${AGENT_PATH}"
fi
# =============================================================================
# SPECIAL AGENT RULESET (with Password field)
# =============================================================================
RULESET_PATH="${BASE_DIR}/rulesets/special_agent.py"
if [[ ! -e "${RULESET_PATH}" ]]; then
cat > "${RULESET_PATH}" <<'EOF'
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Special Agent Ruleset for PLUGIN_FAMILY"""
from cmk.rulesets.v1 import Title, Help
from cmk.rulesets.v1.form_specs import (
DefaultValue,
DictElement,
Dictionary,
migrate_to_password,
Password,
String,
SingleChoice,
SingleChoiceElement,
)
from cmk.rulesets.v1.rule_specs import SpecialAgent, Topic
def _parameter_form_PLUGIN_FAMILY():
return Dictionary(
title=Title("PLUGIN_FAMILY_UPPER API Connection"),
help_text=Help(
"This special agent connects to the PLUGIN_FAMILY_UPPER API "
"using username/password authentication and retrieves monitoring data."
),
elements={
"url": DictElement(
required=True,
parameter_form=String(
title=Title("API Endpoint URL"),
help_text=Help(
"The full URL of the API endpoint. "
"Example: https://api.example.com or https://192.168.1.100:8443"
),
prefill=DefaultValue("https://"),
),
),
"username": DictElement(
required=True,
parameter_form=String(
title=Title("Username"),
help_text=Help("Username for API authentication"),
),
),
"password": DictElement(
required=True,
parameter_form=Password(
title=Title("Password"),
help_text=Help("Password (stored encrypted)"),
migrate=migrate_to_password,
),
),
"insecure": DictElement(
required=False,
parameter_form=SingleChoice(
title=Title("SSL Certificate Verification"),
help_text=Help(
"Choose whether to verify SSL certificates. "
"Disabling verification should only be used for testing!"
),
elements=[
SingleChoiceElement(
name="verify",
title=Title("Verify SSL certificates (recommended)"),
),
SingleChoiceElement(
name="insecure",
title=Title("Disable SSL verification (insecure!)"),
),
],
prefill=DefaultValue("verify"),
),
),
},
)
rule_spec_special_agent_PLUGIN_FAMILY = SpecialAgent(
name="PLUGIN_FAMILY",
title=Title("PLUGIN_FAMILY_UPPER"),
topic=Topic.CLOUD, # Adjust: CLOUD, GENERAL, SERVER_HARDWARE, etc.
parameter_form=_parameter_form_PLUGIN_FAMILY,
)
EOF
sed -i "s/PLUGIN_FAMILY/${AGENT_BASENAME}/g" "${RULESET_PATH}"
sed -i "s/PLUGIN_FAMILY_UPPER/${PLUGIN_FAMILY^^}/g" "${RULESET_PATH}"
chmod 0644 "${RULESET_PATH}"
fi
# =============================================================================
# SERVER-SIDE CALL (with password handling)
# =============================================================================
CALL_PATH="${BASE_DIR}/server_side_calls/special_agent.py"
if [[ ! -e "${CALL_PATH}" ]]; then
cat > "${CALL_PATH}" <<'EOF'
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Server-side call registration for PLUGIN_FAMILY"""
from cmk.server_side_calls.v1 import (
SpecialAgentCommand,
SpecialAgentConfig,
noop_parser,
)
def _agent_arguments(params, host_config):
"""Generate command line arguments for the special agent."""
args = []
# Required parameters
args.extend(["--url", params["url"]])
args.extend(["--username", params["username"]])
args.extend(["--password", params["password"].unsafe()])
# Optional: SSL verification
if params.get("insecure") == "insecure":
args.append("--insecure")
yield SpecialAgentCommand(command_arguments=args)
special_agent_PLUGIN_FAMILY = SpecialAgentConfig(
name="PLUGIN_FAMILY",
parameter_parser=noop_parser,
commands_function=_agent_arguments,
)
EOF
sed -i "s/PLUGIN_FAMILY/${AGENT_BASENAME}/g" "${CALL_PATH}"
chmod 0644 "${CALL_PATH}"
fi
# =============================================================================
# AGENT-BASED CHECK PLUGIN (optional)
# =============================================================================
if [[ "$WITH_CHECKS" == true ]]; then
CHECK_PATH="${BASE_DIR}/agent_based/${AGENT_BASENAME}.py"
if [[ ! -e "${CHECK_PATH}" ]]; then
cat > "${CHECK_PATH}" <<'EOF'
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Agent-based check plugin for PLUGIN_FAMILY"""
import itertools
import json
from cmk.agent_based.v2 import (
AgentSection,
CheckPlugin,
CheckResult,
DiscoveryResult,
Result,
Service,
State,
StringTable,
)
def parse_PLUGIN_FAMILY_status(string_table: StringTable) -> dict | None:
"""
Parse the <<<PLUGIN_FAMILY_status>>> section.
The check plug-in receives the agent section as a two-dimensional
list ('list of lists') of strings. We convert this to a flat list,
concatenate with spaces, and parse as JSON.
See: https://docs.checkmk.com/latest/en/devel_special_agents.html
"""
if not string_table:
return None
try:
# Flatten two-dimensional list to one-dimensional
flatlist = list(itertools.chain.from_iterable(string_table))
# Concatenate array with spaces and parse JSON
parsed = json.loads(" ".join(flatlist))
return parsed
except (json.JSONDecodeError, ValueError):
return None
def discover_PLUGIN_FAMILY_status(section: dict | None) -> DiscoveryResult:
"""Discover the PLUGIN_FAMILY status service."""
if section is not None:
yield Service()
def check_PLUGIN_FAMILY_status(section: dict | None) -> CheckResult:
"""Check the PLUGIN_FAMILY status."""
if section is None:
yield Result(
state=State.UNKNOWN,
summary="No data received from agent"
)
return
status = section.get("status", "UNKNOWN")
code = section.get("code", "N/A")
message = section.get("message", "No message")
# Determine check state based on status
if status == "OK" or code == "200":
state = State.OK
summary = f"API connection successful (Code: {code})"
elif status == "FAILED" or status == "EXCEPTION":
state = State.CRIT
summary = f"API connection failed (Code: {code})"
else:
state = State.WARN
summary = f"Unknown status: {status} (Code: {code})"
yield Result(
state=state,
summary=summary,
details=f"Status: {status}\nCode: {code}\nMessage: {message}"
)
# Section registration
agent_section_PLUGIN_FAMILY_status = AgentSection(
name="PLUGIN_FAMILY_status",
parse_function=parse_PLUGIN_FAMILY_status,
)
# Check plugin registration
check_plugin_PLUGIN_FAMILY_status = CheckPlugin(
name="PLUGIN_FAMILY_status",
service_name="PLUGIN_FAMILY_UPPER Status",
sections=["PLUGIN_FAMILY_status"],
discovery_function=discover_PLUGIN_FAMILY_status,
check_function=check_PLUGIN_FAMILY_status,
)
EOF
sed -i "s/PLUGIN_FAMILY/${AGENT_BASENAME}/g" "${CHECK_PATH}"
sed -i "s/PLUGIN_FAMILY_UPPER/${PLUGIN_FAMILY^^}/g" "${CHECK_PATH}"
chmod 0644 "${CHECK_PATH}"
fi
fi
# =============================================================================
# README
# =============================================================================
README_PATH="${BASE_DIR}/README.md"
if [[ ! -e "${README_PATH}" ]]; then
cat > "${README_PATH}" <<EOF
# ${PLUGIN_FAMILY} β Checkmk Special Agent
Special agent for monitoring ${PLUGIN_FAMILY^^} via API with authentication.
## Features
- Username/Password authentication
- SSL certificate verification (optional disable)
- Encrypted password storage
- HTTP request error handling
- Structured agent output
## Quick Start
### 1. Customize the Agent
Edit \`libexec/agent_${AGENT_BASENAME}\`:
\`\`\`python
# Adjust authentication method (Basic Auth, Token, etc.)
# Modify API endpoints
# Add custom data parsing
\`\`\`
### 2. Activate Plugin
\`\`\`bash
cmk -R
\`\`\`
### 3. Configure in GUI
**Setup β Agents β VM, Cloud, Container β ${PLUGIN_FAMILY^^}**
- Enter API URL
- Provide username/password
- Optional: Disable SSL verification
### 4. Test
\`\`\`bash
# See generated command
cmk -D <HOSTNAME>
# Test agent manually
~/local/lib/python3/cmk_addons/plugins/${PLUGIN_FAMILY}/libexec/agent_${AGENT_BASENAME} \\
--url "https://api.example.com" \\
--username "myuser" \\
--password "mypass"
# Run discovery
cmk -I <HOSTNAME>
\`\`\`
## Authentication Methods
The template supports multiple authentication patterns:
### Basic Auth
\`\`\`python
session.auth = (username, password)
\`\`\`
### Token-based (Login + Bearer)
\`\`\`python
# Login to get token
resp = session.post(url, json={"username": username, "password": password})
token = resp.json()["token"]
# Use token in headers
session.headers.update({"Authorization": f"Bearer {token}"})
\`\`\`
### API Key
\`\`\`python
session.headers.update({"X-API-Key": api_key})
\`\`\`
## Directory Structure
\`\`\`
${PLUGIN_FAMILY}/
βββ libexec/agent_${AGENT_BASENAME} # Special agent (Python)
βββ rulesets/special_agent.py # GUI configuration
βββ server_side_calls/special_agent.py # Command generation
EOF
if [[ "$WITH_CHECKS" == true ]]; then
cat >> "${README_PATH}" <<EOF
βββ agent_based/${AGENT_BASENAME}.py # Check plugin
EOF
fi
cat >> "${README_PATH}" <<EOF
βββ README.md
\`\`\`
## Password Security
- Passwords are stored encrypted in Checkmk
- Use \`params["password"].unsafe()\` to access in server_side_calls
- Never log passwords in plain text
## References
- [Introduction to Developing Extensions](https://docs.checkmk.com/latest/en/devel_intro.html)
- [Developing Special Agents](https://docs.checkmk.com/latest/en/devel_special_agents.html)
- [Developing Agent-based Check Plug-ins](https://docs.checkmk.com/latest/en/devel_check_plugins.html)
- [Rulesets API](https://docs.checkmk.com/latest/en/devel_check_plugins_rulesets.html)
- [Password Handling in Rulesets](https://docs.checkmk.com/latest/en/devel_check_plugins_rulesets.html#passwords)
- **In Checkmk GUI:** Help > Developer resources > Plug-in API references
EOF
fi
# =============================================================================
# OWNERSHIP
# =============================================================================
if [[ -n "${SITE_USER}" ]]; then
chown -R "${SITE_USER}:${SITE_USER}" "${BASE_DIR}"
fi
# =============================================================================
# SUMMARY
# =============================================================================
echo "========================================="
echo "Special Agent Skeleton Created!"
echo "========================================="
echo "Plugin: ${PLUGIN_FAMILY}"
echo "Location: ${BASE_DIR}"
echo ""
echo "Files created:"
echo " β ${AGENT_PATH}"
echo " β ${RULESET_PATH}"
echo " β ${CALL_PATH}"
if [[ "$WITH_CHECKS" == true ]]; then
echo " β ${CHECK_PATH}"
fi
echo " β ${README_PATH}"
echo ""
echo "Features:"
echo " β Username/Password authentication"
echo " β Encrypted password storage"
echo " β SSL verification (configurable)"
echo " β HTTP requests with error handling"
echo ""
echo "Next steps:"
echo " 1. Edit libexec/agent_${AGENT_BASENAME} - customize API calls"
echo " 2. Run: cmk -R"
echo " 3. Configure in Setup β Agents β VM, Cloud, Container"
echo " 4. Test with: cmk -D <HOSTNAME>"
echo ""
and i had extend the restart script for development β¦
Call & Output
OMD[test]:~$ ./dev_restart_adv.sh --help
Usage: ./dev_restart_adv.sh [OPTIONS]
Restart Checkmk development environment with various options.
Options:
--full, -f Full OMD restart (all services)
--clean, -c Clean Python cache before restart
--validate, -v Validate Python syntax before restart
--log, -l Tail web.log after restart
--discover <HOST> Run service discovery on HOST after restart
--verbose Show detailed output
--help, -h Show this help
Examples:
./dev_restart_adv.sh # Quick restart
./dev_restart_adv.sh --clean # Clean + restart
./dev_restart_adv.sh --full --clean # Full restart with cache clean
./dev_restart_adv.sh -c -v # Clean + validate + restart
./dev_restart_adv.sh --clean --discover myhost # Clean + restart + discovery
./dev_restart_adv.sh --log # Restart + monitor logs
Development Workflow:
1. Make code changes
2. Run: ./dev_restart_adv.sh --clean --validate
3. Test in GUI or with: cmk -I <hostname>
4. Check logs with: tail -f ~/var/log/web.log
#!/bin/bash
# =============================================================================
# Checkmk Advanced Development Restart Script
# =============================================================================
# Comprehensive restart script for Checkmk plugin development with:
# - Selective service restarts
# - Python cache cleaning
# - Syntax validation
# - Log monitoring
# - Auto-discovery option
# =============================================================================
set -euo pipefail
# ----- Configuration ----------------------------------------------------------
QUICK_SERVICES=("automation-helper" "ui-job-scheduler" "apache")
SITE_NAME=$(omd config show CORE 2>/dev/null | head -1 || echo "unknown")
# Flags
FULL_MODE=false
CLEAN_CACHE=false
VALIDATE=false
TAIL_LOG=false
DISCOVER_HOST=""
VERBOSE=false
# ----- Parse arguments --------------------------------------------------------
while [[ $# -gt 0 ]]; do
case "$1" in
--full|-f)
FULL_MODE=true
shift
;;
--clean|-c)
CLEAN_CACHE=true
shift
;;
--validate|-v)
VALIDATE=true
shift
;;
--log|-l)
TAIL_LOG=true
shift
;;
--discover)
DISCOVER_HOST="$2"
shift 2
;;
--verbose)
VERBOSE=true
shift
;;
--help|-h)
cat <<EOF
Usage: $0 [OPTIONS]
Restart Checkmk development environment with various options.
Options:
--full, -f Full OMD restart (all services)
--clean, -c Clean Python cache before restart
--validate, -v Validate Python syntax before restart
--log, -l Tail web.log after restart
--discover <HOST> Run service discovery on HOST after restart
--verbose Show detailed output
--help, -h Show this help
Examples:
$0 # Quick restart
$0 --clean # Clean + restart
$0 --full --clean # Full restart with cache clean
$0 -c -v # Clean + validate + restart
$0 --clean --discover myhost # Clean + restart + discovery
$0 --log # Restart + monitor logs
Development Workflow:
1. Make code changes
2. Run: $0 --clean --validate
3. Test in GUI or with: cmk -I <hostname>
4. Check logs with: tail -f ~/var/log/web.log
EOF
exit 0
;;
*)
echo "Unknown option: $1" >&2
echo "Use --help for usage information"
exit 1
;;
esac
done
# ----- Helper functions -------------------------------------------------------
log_step() {
echo "==> $1"
}
log_success() {
echo " β $1"
}
log_warn() {
echo " β Warning: $1" >&2
}
log_error() {
echo " β Error: $1" >&2
}
# ----- Validate Python syntax (optional) --------------------------------------
if [[ "$VALIDATE" == true ]]; then
log_step "Validating Python syntax..."
ERROR_COUNT=0
# Check all Python files in cmk_addons
while IFS= read -r -d '' pyfile; do
if ! python3 -m py_compile "$pyfile" 2>/dev/null; then
log_error "Syntax error in: $pyfile"
ERROR_COUNT=$((ERROR_COUNT + 1))
elif [[ "$VERBOSE" == true ]]; then
log_success "OK: $pyfile"
fi
done < <(find ~/local/lib/python3/cmk_addons -name "*.py" -print0 2>/dev/null)
if [[ $ERROR_COUNT -gt 0 ]]; then
log_error "Found $ERROR_COUNT syntax error(s). Fix them before restarting!"
exit 1
fi
log_success "All Python files validated"
fi
# ----- Clean Python cache (optional) ------------------------------------------
if [[ "$CLEAN_CACHE" == true ]]; then
log_step "Cleaning Python cache..."
CACHE_DIRS=0
CACHE_FILES=0
# Count and remove __pycache__ directories
while IFS= read -r dir; do
CACHE_DIRS=$((CACHE_DIRS + 1))
done < <(find ~/local/lib/python3/cmk_addons -type d -name "__pycache__" 2>/dev/null)
find ~/local/lib/python3/cmk_addons -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
# Count and remove .pyc files
while IFS= read -r file; do
CACHE_FILES=$((CACHE_FILES + 1))
done < <(find ~/local/lib/python3/cmk_addons -name "*.pyc" 2>/dev/null)
find ~/local/lib/python3/cmk_addons -name "*.pyc" -delete 2>/dev/null || true
if [[ "$VERBOSE" == true ]]; then
log_success "Removed $CACHE_DIRS cache directories and $CACHE_FILES .pyc files"
else
log_success "Cache cleaned"
fi
fi
# ----- Restart services -------------------------------------------------------
if [[ "$FULL_MODE" == true ]]; then
log_step "Full OMD restart..."
if [[ "$VERBOSE" == true ]]; then
omd stop
sleep 1
omd start
else
omd stop >/dev/null 2>&1
sleep 1
omd start >/dev/null 2>&1
fi
log_success "All services restarted"
else
log_step "Quick restart (GUI services only)..."
for service in "${QUICK_SERVICES[@]}"; do
if [[ "$VERBOSE" == true ]]; then
echo " Restarting $service..."
fi
if omd restart "$service" >/dev/null 2>&1; then
if [[ "$VERBOSE" == true ]]; then
log_success "$service restarted"
fi
else
log_warn "Could not restart $service (may not be running)"
fi
done
log_success "GUI services restarted"
fi
# ----- Reload Checkmk configuration -------------------------------------------
log_step "Reloading Checkmk configuration..."
if [[ "$VERBOSE" == true ]]; then
cmk -R
else
cmk -R >/dev/null 2>&1
fi
log_success "Configuration reloaded"
# ----- Run service discovery (optional) ---------------------------------------
if [[ -n "$DISCOVER_HOST" ]]; then
log_step "Running service discovery on $DISCOVER_HOST..."
if cmk -I "$DISCOVER_HOST" 2>&1 | grep -q "SUCCESS"; then
log_success "Discovery completed for $DISCOVER_HOST"
else
log_warn "Discovery may have failed for $DISCOVER_HOST"
fi
fi
# ----- Status summary ---------------------------------------------------------
echo ""
echo "========================================="
echo "Development Environment Ready!"
echo "========================================="
[[ "$VALIDATE" == true ]] && echo "β Python syntax validated"
[[ "$CLEAN_CACHE" == true ]] && echo "β Python cache cleaned"
[[ "$FULL_MODE" == true ]] && echo "β All OMD services restarted" || echo "β GUI services restarted"
echo "β Configuration reloaded"
[[ -n "$DISCOVER_HOST" ]] && echo "β Discovery run on $DISCOVER_HOST"
echo ""
echo "Tips:"
echo " β’ Check service status: omd status"
echo " β’ View web logs: tail -f ~/var/log/web.log"
echo " β’ Test check: cmk -vvn <HOSTNAME>"
echo " β’ List plugins: cmk --list-checks | grep <name>"
# ----- Tail logs (optional) ---------------------------------------------------
if [[ "$TAIL_LOG" == true ]]; then
echo ""
echo "==> Monitoring ~/var/log/web.log (Ctrl+C to stop)..."
echo ""
tail -f ~/var/log/web.log
fi
debug script for CustomSidebarSnapins:
Call & Output
OMD[test]:~$ python3 debug_registration_snapin.py
==========================================================================================
CHECKMK SIDEBAR SNAPIN REGISTRATION DEBUGGER
==========================================================================================
Run at: 2026-02-13 08:55:27
Scanning directory: /omd/sites/test/local/lib/python3/cmk/gui/plugins/sidebar
Found 2 Python files:
β’ simple_test_snapin.py
β’ ticket_test.py
==========================================================================================
Testing import of each snapin...
β Testing: simple_test_snapin
β Module imported successfully
β Found snapin class(es): ['CustomizableSidebarSnapin', 'HostMatrixSnapin']
----------------------------------------------------------------------
β Testing: ticket_test
β Module imported successfully
β Found snapin class(es): ['CustomizableSidebarSnapin', 'TicketTestSnapin']
----------------------------------------------------------------------
==========================================================================================
FINAL SUMMARY
==========================================================================================
Total files scanned : 2
Successfully loaded : 2
Failed / problematic : 0
SUCCESSFUL SNAPINS:
β simple_test_snapin β ['CustomizableSidebarSnapin', 'HostMatrixSnapin']
β ticket_test β ['CustomizableSidebarSnapin', 'TicketTestSnapin']
==========================================================================================
EMPFEHLUNGEN:
β’ Wenn ein Snapin 'IMPORT_ERROR' zeigt β falscher Import-Pfad
β’ Wenn 'NO_SNAPIN' β fehlender @snapin_registry.register
β’ Wenn 'EXCEPTION' β Syntax- oder Laufzeitfehler in der Datei
==========================================================================================
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# debug_snapins.py - Detaillierter Snapin-Import-Debugger
import sys
import os
import importlib
import traceback
from pathlib import Path
from datetime import datetime
print("=" * 90)
print("CHECKMK SIDEBAR SNAPIN REGISTRATION DEBUGGER")
print("=" * 90)
print(f"Run at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
# 1. Verzeichnis ermitteln
site_root = os.environ.get('OMD_ROOT')
if not site_root:
print("ERROR: OMD_ROOT Umgebungsvariable nicht gesetzt!")
sys.exit(1)
plugin_dir = Path(site_root) / "local" / "lib" / "python3" / "cmk" / "gui" / "plugins" / "sidebar"
print(f"Scanning directory: {plugin_dir}\n")
# 2. Alle .py Dateien finden (auΓer __init__.py)
py_files = [f for f in plugin_dir.glob("*.py") if f.name != "__init__.py"]
print(f"Found {len(py_files)} Python files:\n")
for f in sorted(py_files):
print(f" β’ {f.name}")
print("\n" + "=" * 90)
print("Testing import of each snapin...\n")
results = []
for py_file in sorted(py_files):
module_name = py_file.stem
full_module = f"cmk.gui.plugins.sidebar.{module_name}"
print(f"β Testing: {module_name}")
try:
# Modul importieren
module = importlib.import_module(full_module)
print(" β Module imported successfully")
# PrΓΌfen auf Snapin-Registrierung
snapin_classes = []
for name, obj in module.__dict__.items():
if isinstance(obj, type) and hasattr(obj, 'type_name'):
snapin_classes.append(name)
if snapin_classes:
print(f" β Found snapin class(es): {snapin_classes}")
results.append((module_name, "SUCCESS", snapin_classes))
else:
print(" β No snapin class found (missing @snapin_registry.register?)")
results.append((module_name, "NO_SNAPIN", []))
except ImportError as e:
print(f" β ImportError: {e}")
results.append((module_name, "IMPORT_ERROR", str(e)))
except Exception as e:
print(f" β Exception: {e}")
traceback.print_exc()
results.append((module_name, "EXCEPTION", str(e)))
print("-" * 70)
# 3. Zusammenfassung
print("\n" + "=" * 90)
print("FINAL SUMMARY")
print("=" * 90)
success = [r for r in results if r[1] == "SUCCESS"]
failed = [r for r in results if r[1] != "SUCCESS"]
print(f"Total files scanned : {len(results)}")
print(f"Successfully loaded : {len(success)}")
print(f"Failed / problematic : {len(failed)}\n")
if success:
print("SUCCESSFUL SNAPINS:")
for name, _, classes in success:
print(f" β {name} β {classes}")
if failed:
print("\nPROBLEMATIC FILES:")
for name, status, details in failed:
print(f" β {name}: {status}")
if details:
print(f" β {details[:100]}...")
print("\n" + "=" * 90)
print("EMPFEHLUNGEN:")
print("β’ Wenn ein Snapin 'IMPORT_ERROR' zeigt β falscher Import-Pfad")
print("β’ Wenn 'NO_SNAPIN' β fehlender @snapin_registry.register")
print("β’ Wenn 'EXCEPTION' β Syntax- oder Laufzeitfehler in der Datei")
print("=" * 90)