Small Helper Scripts for CLI

Hi,
here some small helpers … feel free to add

mutch better :wink:

thats the reason … share to get better :wink:

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)
4 Likes

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?

2 Likes

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
3 Likes

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/&gt;/>/g' "$target"
        sed -i 's/&lt;/</g' "$target"
    fi
done

echo
echo "Done."
Sample call and output

`

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.

`

1 Like

and another script to create the hole plugin Folder structure

call

OMD[test]:~$ ./create_plugin_structure.sh xiq

create_plugin_structure.sh

`

#!/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
1 Like

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>

special_agent_skeleton.sh
#!/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 ""
3 Likes

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
dev_restart_adv.sh
#!/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
2 Likes

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
==========================================================================================

debug_registration_snapin.py
#!/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)