#!/usr/bin/env python3 """Generate the external BizTalk Checkmk Pulse architecture overview as DOCX.""" from pathlib import Path from docx import Document from docx.enum.section import WD_SECTION from docx.enum.table import WD_ALIGN_VERTICAL, WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK from docx.oxml import OxmlElement from docx.oxml.ns import qn from docx.shared import Cm, Inches, Pt, RGBColor ROOT = Path(__file__).resolve().parents[1] DOCS = ROOT / "docs" OUTPUT = DOCS / "BizTalk_Checkmk_Pulse_Architekturueberblick.docx" ARCHITECTURE_IMAGE = DOCS / "architecture.png" NAVY = "17365D" BLUE = "24547C" MID_BLUE = "2B6F9F" LIGHT_BLUE = "EEF6FB" LIGHTER_BLUE = "F4F7FA" GREEN = "16845B" LIGHT_GREEN = "ECFDF5" ORANGE = "DD6B20" LIGHT_ORANGE = "FFF7ED" TEXT = "1F2937" MUTED = "5B6770" GRID = "CBD5E1" WHITE = "FFFFFF" def set_cell_shading(cell, fill): tc_pr = cell._tc.get_or_add_tcPr() shd = tc_pr.find(qn("w:shd")) if shd is None: shd = OxmlElement("w:shd") tc_pr.append(shd) shd.set(qn("w:fill"), fill) def set_cell_margins(cell, top=120, start=120, bottom=120, end=120): tc = cell._tc tc_pr = tc.get_or_add_tcPr() tc_mar = tc_pr.first_child_found_in("w:tcMar") if tc_mar is None: tc_mar = OxmlElement("w:tcMar") tc_pr.append(tc_mar) for margin, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)): node = tc_mar.find(qn(f"w:{margin}")) if node is None: node = OxmlElement(f"w:{margin}") tc_mar.append(node) node.set(qn("w:w"), str(value)) node.set(qn("w:type"), "dxa") def set_repeat_table_header(row): tr_pr = row._tr.get_or_add_trPr() repeat = OxmlElement("w:tblHeader") repeat.set(qn("w:val"), "true") tr_pr.append(repeat) def prevent_row_split(row): tr_pr = row._tr.get_or_add_trPr() cant_split = OxmlElement("w:cantSplit") tr_pr.append(cant_split) def set_table_fixed(table): table.autofit = False tbl_pr = table._tbl.tblPr layout = tbl_pr.find(qn("w:tblLayout")) if layout is None: layout = OxmlElement("w:tblLayout") tbl_pr.append(layout) layout.set(qn("w:type"), "fixed") def set_paragraph_shading(paragraph, fill, border=None): p_pr = paragraph._p.get_or_add_pPr() shd = OxmlElement("w:shd") shd.set(qn("w:fill"), fill) p_pr.append(shd) if border: p_bdr = OxmlElement("w:pBdr") left = OxmlElement("w:left") left.set(qn("w:val"), "single") left.set(qn("w:sz"), "20") left.set(qn("w:space"), "8") left.set(qn("w:color"), border) p_bdr.append(left) p_pr.append(p_bdr) def add_bottom_border(paragraph, color="8FB8DC"): p_pr = paragraph._p.get_or_add_pPr() p_bdr = OxmlElement("w:pBdr") bottom = OxmlElement("w:bottom") bottom.set(qn("w:val"), "single") bottom.set(qn("w:sz"), "8") bottom.set(qn("w:space"), "5") bottom.set(qn("w:color"), color) p_bdr.append(bottom) p_pr.append(p_bdr) def set_keep(paragraph, keep_next=False, keep_lines=True): p_pr = paragraph._p.get_or_add_pPr() if keep_next: p_pr.append(OxmlElement("w:keepNext")) if keep_lines: p_pr.append(OxmlElement("w:keepLines")) def add_page_field(paragraph): run = paragraph.add_run() fld_char_1 = OxmlElement("w:fldChar") fld_char_1.set(qn("w:fldCharType"), "begin") instr_text = OxmlElement("w:instrText") instr_text.set(qn("xml:space"), "preserve") instr_text.text = " PAGE " fld_char_2 = OxmlElement("w:fldChar") fld_char_2.set(qn("w:fldCharType"), "end") run._r.extend((fld_char_1, instr_text, fld_char_2)) def format_run(run, *, size=None, bold=None, color=None, italic=None, font="Liberation Sans"): run.font.name = font run._element.rPr.rFonts.set(qn("w:eastAsia"), font) if size is not None: run.font.size = Pt(size) if bold is not None: run.bold = bold if color: run.font.color.rgb = RGBColor.from_string(color) if italic is not None: run.italic = italic return run def add_rich_paragraph(document, parts, *, style=None, before=0, after=5, align=None): paragraph = document.add_paragraph(style=style) paragraph.paragraph_format.space_before = Pt(before) paragraph.paragraph_format.space_after = Pt(after) paragraph.paragraph_format.line_spacing = 1.12 if align is not None: paragraph.alignment = align for text, options in parts: run = paragraph.add_run(text) format_run(run, **options) return paragraph def add_body(document, text, *, bold_lead=None, after=5): paragraph = document.add_paragraph() paragraph.paragraph_format.space_after = Pt(after) paragraph.paragraph_format.line_spacing = 1.13 if bold_lead and text.startswith(bold_lead): format_run(paragraph.add_run(bold_lead), bold=True, color=TEXT) format_run(paragraph.add_run(text[len(bold_lead):]), color=TEXT) else: format_run(paragraph.add_run(text), color=TEXT) return paragraph def add_heading(document, text, level=1): paragraph = document.add_paragraph() paragraph.paragraph_format.space_before = Pt(12 if level == 1 else 8) paragraph.paragraph_format.space_after = Pt(6 if level == 1 else 4) size = 17 if level == 1 else 12 format_run(paragraph.add_run(text), size=size, bold=True, color=NAVY) if level == 1: add_bottom_border(paragraph) set_keep(paragraph, keep_next=True) return paragraph def add_bullet(document, text, *, level=0): paragraph = document.add_paragraph(style="List Bullet" if level == 0 else "List Bullet 2") paragraph.paragraph_format.left_indent = Cm(0.65 + 0.45 * level) paragraph.paragraph_format.first_line_indent = Cm(-0.25) paragraph.paragraph_format.space_after = Pt(2.5) paragraph.paragraph_format.line_spacing = 1.08 format_run(paragraph.add_run(text), color=TEXT) return paragraph def add_numbered(document, text): paragraph = document.add_paragraph(style="List Number") paragraph.paragraph_format.left_indent = Cm(0.7) paragraph.paragraph_format.first_line_indent = Cm(-0.3) paragraph.paragraph_format.space_after = Pt(3) paragraph.paragraph_format.line_spacing = 1.08 format_run(paragraph.add_run(text), color=TEXT) return paragraph def add_callout(document, label, text, *, fill=LIGHT_BLUE, border=MID_BLUE): table = document.add_table(rows=1, cols=1) table.alignment = WD_TABLE_ALIGNMENT.CENTER table.autofit = False cell = table.cell(0, 0) cell.width = Cm(17.2) set_cell_shading(cell, fill) set_cell_margins(cell, top=170, start=230, bottom=170, end=230) paragraph = cell.paragraphs[0] paragraph.paragraph_format.space_after = Pt(0) paragraph.paragraph_format.line_spacing = 1.12 format_run(paragraph.add_run(label), bold=True, color=border) format_run(paragraph.add_run(text), color=TEXT) table.rows[0]._tr.get_or_add_trPr().append(OxmlElement("w:cantSplit")) after = document.add_paragraph() after.paragraph_format.space_after = Pt(1) return table def add_table(document, headers, rows, widths, *, font_size=8.7): table = document.add_table(rows=1, cols=len(headers)) table.style = "Table Grid" table.alignment = WD_TABLE_ALIGNMENT.CENTER set_table_fixed(table) header = table.rows[0] set_repeat_table_header(header) prevent_row_split(header) for index, title in enumerate(headers): cell = header.cells[index] cell.width = Cm(widths[index]) cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER set_cell_shading(cell, BLUE) set_cell_margins(cell, top=100, start=110, bottom=100, end=110) p = cell.paragraphs[0] p.paragraph_format.space_after = Pt(0) format_run(p.add_run(title), bold=True, color=WHITE, size=font_size) for row_index, values in enumerate(rows): row = table.add_row() prevent_row_split(row) for col_index, value in enumerate(values): cell = row.cells[col_index] cell.width = Cm(widths[col_index]) cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.TOP set_cell_margins(cell, top=90, start=110, bottom=90, end=110) if row_index % 2 == 1: set_cell_shading(cell, LIGHTER_BLUE) p = cell.paragraphs[0] p.paragraph_format.space_after = Pt(0) p.paragraph_format.line_spacing = 1.03 format_run(p.add_run(str(value)), color=TEXT, size=font_size) spacer = document.add_paragraph() spacer.paragraph_format.space_after = Pt(1) return table def add_code_block(document, lines): paragraph = document.add_paragraph() paragraph.paragraph_format.left_indent = Cm(0.15) paragraph.paragraph_format.right_indent = Cm(0.15) paragraph.paragraph_format.space_before = Pt(3) paragraph.paragraph_format.space_after = Pt(7) paragraph.paragraph_format.line_spacing = 1.0 set_paragraph_shading(paragraph, "F3F6F8", MID_BLUE) for index, line in enumerate(lines): run = paragraph.add_run(line) format_run(run, font="Liberation Mono", size=7.8, color="0F3E5E") if index < len(lines) - 1: run.add_break() set_keep(paragraph) return paragraph def configure_document(document): section = document.sections[0] section.page_width = Cm(21.0) section.page_height = Cm(29.7) section.top_margin = Cm(1.55) section.bottom_margin = Cm(1.55) section.left_margin = Cm(1.7) section.right_margin = Cm(1.7) section.header_distance = Cm(0.65) section.footer_distance = Cm(0.65) section.different_first_page_header_footer = True styles = document.styles normal = styles["Normal"] normal.font.name = "Liberation Sans" normal._element.rPr.rFonts.set(qn("w:eastAsia"), "Liberation Sans") normal.font.size = Pt(9.6) normal.font.color.rgb = RGBColor.from_string(TEXT) normal.paragraph_format.space_after = Pt(4) for name in ("List Bullet", "List Bullet 2", "List Number"): styles[name].font.name = "Liberation Sans" styles[name]._element.rPr.rFonts.set(qn("w:eastAsia"), "Liberation Sans") styles[name].font.size = Pt(9.3) header = section.header p = header.paragraphs[0] p.alignment = WD_ALIGN_PARAGRAPH.RIGHT p.paragraph_format.space_after = Pt(0) format_run(p.add_run("BizTalk Checkmk Pulse | Architektur- und Lösungsüberblick"), size=7.5, color=MUTED) footer = section.footer p = footer.paragraphs[0] p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.paragraph_format.space_after = Pt(0) format_run(p.add_run("Version 2.2.6 | Stand 11.08.2026 | Seite "), size=7.5, color=MUTED) add_page_field(p) def build_document(): if not ARCHITECTURE_IMAGE.exists(): raise FileNotFoundError(f"Architecture image not found: {ARCHITECTURE_IMAGE}") document = Document() configure_document(document) core = document.core_properties core.title = "BizTalk Checkmk Pulse – Architektur- und Lösungsüberblick" core.subject = "Gesamtarchitektur, Funktionsumfang und Datenaustausch" core.author = "BEW" core.comments = "Externe Lösungsübersicht zum implementierten Stand 2.2.6" # Cover banner = document.add_table(rows=1, cols=1) banner.autofit = False banner.cell(0, 0).width = Cm(17.3) set_cell_shading(banner.cell(0, 0), NAVY) set_cell_margins(banner.cell(0, 0), top=170, start=170, bottom=170, end=170) p = banner.cell(0, 0).paragraphs[0] p.paragraph_format.space_after = Pt(0) format_run(p.add_run("LÖSUNGSÜBERSICHT · EXTERNE DARSTELLUNG"), bold=True, color=WHITE, size=9) p = document.add_paragraph() p.paragraph_format.space_before = Pt(32) p.paragraph_format.space_after = Pt(10) format_run(p.add_run("BizTalk Checkmk Pulse"), size=28, bold=True, color=NAVY) p = document.add_paragraph() p.paragraph_format.space_after = Pt(26) format_run(p.add_run("Architektur, Funktionsumfang und Datenaustausch des lokalen Checkmk-Monitorings für Microsoft BizTalk Server"), size=15, color="4B6478") add_callout( document, "", "Die Lösung übersetzt den technischen Laufzeitzustand einer BizTalk-Umgebung in neun kompakte Checkmk-Services. Ein dediziertes Konto erfasst die benötigten Daten; der Checkmk-Agent selbst erhält keine BizTalk- oder SQL-Berechtigungen.", fill=BLUE, border=WHITE, ) # Callout helper uses dark text; force white on the cover callout. for run in document.tables[-1].cell(0, 0).paragraphs[0].runs: run.font.color.rgb = RGBColor.from_string(WHITE) run.font.size = Pt(12.5) facts = document.add_table(rows=1, cols=4) facts.alignment = WD_TABLE_ALIGNMENT.CENTER set_table_fixed(facts) for cell, value, label in zip( facts.rows[0].cells, ("9", "1 min", "180 s", "Read-only"), ("stabile Services", "Sammelintervall", "Frischegrenze", "BizTalk-Zugriff"), ): cell.width = Cm(4.3) set_cell_shading(cell, "EAF2F8") set_cell_margins(cell, top=180, start=80, bottom=180, end=80) p = cell.paragraphs[0] p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.paragraph_format.space_after = Pt(2) format_run(p.add_run(value), size=15, bold=True, color=NAVY) p = cell.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.paragraph_format.space_after = Pt(0) format_run(p.add_run(label), size=8.3, color=TEXT) document.add_paragraph().paragraph_format.space_after = Pt(3) add_table( document, ("Dokumentmerkmal", "Angabe"), ( ("Lösungsstand", "BizTalk Checkmk Pulse 2.2.6"), ("Zielplattform", "Microsoft BizTalk Server 2020 · Windows Server 2019 · Checkmk 2.4"), ("Dokumentstand", "11. August 2026"), ("Dokumentzweck", "Architektur- und Leistungsüberblick für technische Stakeholder"), ), (4.2, 13.0), font_size=8.8, ) add_rich_paragraph( document, (("Dieses Dokument beschreibt den implementierten Lösungsstand. Umgebungsspezifische Parameter und die jeweilige Betriebsfreigabe werden im Rollout bestätigt.", {"size": 8.2, "color": MUTED}),), before=10, after=0, ) document.add_page_break() # 1–2: Summary and Checkmk overview add_heading(document, "1. Zusammenfassung") add_body(document, "BizTalk Checkmk Pulse ergänzt das zentrale Monitoring um eine BizTalk-spezifische Sicht. Die Lösung erkennt typische Betriebsstörungen – zum Beispiel suspendierte Instanzen, gestoppte Host Instances, unerwartet inaktive Ports, nicht erreichbare externe Ziele oder aktuelle BizTalk-Ereignisse – und stellt sie als eigenständige Services in Checkmk dar.") add_body(document, "Die Architektur folgt einer klaren Aufgabentrennung:") add_numbered(document, "Ein minütlicher Windows Scheduled Task sammelt unter einem dedizierten, eingeschränkt berechtigten AD-Servicekonto die BizTalk-Betriebsdaten.") add_numbered(document, "Die Ergebnisse werden in einen kompakten, versionierten und integritätsgeprüften lokalen Snapshot geschrieben.") add_numbered(document, "Der Checkmk Windows Agent läuft weiterhin als LocalSystem und liest ausschließlich diesen Snapshot.") add_numbered(document, "Checkmk übernimmt Status, Metriken und Kurztexte in das zentrale Monitoring.") add_callout(document, "Nutzen: ", "BizTalk-Störungen werden zentral sichtbar, ohne dem Checkmk-Agenten direkte BizTalk- oder SQL-Rechte zu erteilen. Die Ausgabe bleibt kompakt, graphfähig und alarmierbar.", fill=LIGHT_GREEN, border=GREEN) add_heading(document, "2. Was ist Checkmk – und was ist ein Local Check?") add_body(document, "Checkmk ist eine zentrale Monitoring-Plattform für IT-Infrastrukturen und Anwendungen. Auf überwachten Servern liefert ein Agent technische Zustände und Messwerte an die Checkmk-Instanz. Dort werden die Daten als Hosts und Services dargestellt, historisiert und für Dashboards, Schwellwerte und Benachrichtigungen verwendet.") add_body(document, "Ein Local Check ist eine bewusst einfache Erweiterungsmöglichkeit des Checkmk-Agenten. Die Prüfung läuft auf dem Zielsystem und gibt pro Service eine Textzeile mit vier Bestandteilen aus:") add_code_block(document, ('0 "BizTalk Send Ports" biztalk_send_ports_total=31;;;0|biztalk_send_ports_started=31;;;0', 'Alle Send Ports sind aktiv.')) add_table( document, ("Bestandteil", "Bedeutung"), ( ("0", "Status: 0 OK, 1 WARN, 2 CRIT, 3 UNKNOWN"), ('"BizTalk …"', "Eindeutiger Servicename, der in Checkmk angezeigt wird"), ("biztalk_…=Wert", "Metriken für Schwellwerte, Auswertungen und Zeitreihen"), ("Kurztext", "Lesbare Zusammenfassung; bei Fehlern mit begrenzter Liste betroffener Objekte"), ), (4.0, 13.2), ) add_body(document, "Der lokale Check dieser Lösung führt im Agentenpfad keine aufwendige BizTalk-Abfrage aus. Er startet lediglich den Consumer, validiert den vorbereiteten Snapshot und schreibt die bereits erzeugten Checkmk-Zeilen auf die Standardausgabe. Damit bleibt der Agentenaufruf schnell und seine Berechtigungsfläche klein.") add_rich_paragraph( document, ( ("Referenz: ", {"size": 8.0, "bold": True, "color": MUTED}), ("Checkmk User Guide, „Local checks“ – https://docs.checkmk.com/latest/en/localchecks.html", {"size": 8.0, "color": MID_BLUE}), ), after=0, ) document.add_page_break() # 3: Architecture add_heading(document, "3. Gesamtarchitektur") p = document.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.paragraph_format.space_after = Pt(3) run = p.add_run() run.add_picture(str(ARCHITECTURE_IMAGE), width=Cm(16.9)) caption = document.add_paragraph() caption.alignment = WD_ALIGN_PARAGRAPH.CENTER caption.paragraph_format.space_after = Pt(6) format_run(caption.add_run("Abbildung 1: Trennung von privilegierter Datenerfassung, lokalem Datentransport und Checkmk-Consumer"), size=7.8, color=MUTED, italic=True) add_table( document, ("Baustein", "Aufgabe", "Berechtigungsprofil"), ( ("Scheduled Task", "Startet den Collector jede Minute und verhindert parallele Läufe.", "Dediziertes AD-Servicekonto, RunLevel Limited"), ("Collector (--collect)", "Liest BizTalk-WMI, prüft integrierten SQL-Zugriff, Event Log und aktive Endpunkte.", "BizTalk Read-only; kein lokaler Administrator, kein SQL-sysadmin"), ("Snapshot & Katalog", "Lokaler, ACL-geschützter Übergabepunkt zwischen Collector und Consumer.", "Collector schreibt; LocalSystem liest"), ("Consumer (--consume)", "Prüft Format, Maschine, Alter, Zeilenanzahl und SHA-256; gibt gültige Checkmk-Zeilen aus.", "LocalSystem; kein WMI-, SQL- oder Endpoint-Zugriff"), ("Checkmk", "Erkennt neun Services, übernimmt Metriken und löst regelbasiert Benachrichtigungen aus.", "Zentraler Monitoring-Betrieb"), ), (3.5, 8.1, 5.6), font_size=7.9, ) add_callout(document, "Fail-safe-Verhalten: ", "Fehlt der Snapshot, ist er älter als standardmäßig 180 Sekunden oder schlägt eine Integritätsprüfung fehl, liefert der Consumer neun gültige UNKNOWN-Services. Ein Transportfehler wird sichtbar und nicht als gesunder Zustand interpretiert.") add_heading(document, "Architekturprinzipien", level=2) add_bullet(document, "Least Privilege: Nur das Collector-Konto erhält den erforderlichen lesenden BizTalk-Zugriff.") add_bullet(document, "Atomare Übergabe: Der Collector ersetzt den Snapshot erst nach vollständigem Schreiben.") add_bullet(document, "Begrenzte Laufzeit: Timeouts, Parallelität, maximale Endpunktzahl und Textlängen sind konfiguriert.") add_bullet(document, "Stabile Schnittstelle: Die neun Basisservices bleiben auch bei Fehlern vorhanden.") add_bullet(document, "Stabile Namen: Die Umgebungskennzeichnung ändert vorhandene Checkmk-Services standardmäßig nicht.") document.add_page_break() # 4: Monitoring scope add_heading(document, "4. Was überwacht die Lösung?") add_body(document, "Die Lösung erzeugt standardmäßig neun kompakte Services. Die einzelnen Zustände können in Checkmk separat visualisiert, alarmiert und historisiert werden.") add_table( document, ("Service", "Überwachte Aussage", "Typische Messwerte"), ( ("BizTalk Platform", "WMI-Zugang, BizTalk-Gruppe und zentrale Datenbankziele sind ermittelbar.", "Plattform- und Zielinformationen"), ("BizTalk SQL Access", "Collector-Konto kann die BizTalk-Datenbanken per Windows-Authentifizierung öffnen.", "Ziele gesamt, erreichbar, fehlgeschlagen"), ("BizTalk Suspended Instances", "Resumable/non-resumable Instanzen und Routing Failure Reports.", "Anzahlen je Kategorie"), ("BizTalk Host Instances", "Lokale Host Instances laufen oder befinden sich in Stop-/Übergangszuständen.", "Started, stopped, pending, unknown"), ("BizTalk Receive Locations", "Aktivierungen sowie erwartete oder unerwartete Deaktivierungen.", "Enabled, expected/unexpected disabled"), ("BizTalk Send Ports", "Gestartete sowie erwartet oder unerwartet inaktive Send Ports.", "Started, stopped, bound, unknown"), ("BizTalk Endpoint Reachability", "Technische Ziele aktiver Send-/Receive-Artefakte sind über Host/Port erreichbar.", "Aktiv, getestet, erreichbar, fehlerhaft, Dauer"), ("BizTalk Orchestrations", "Gestartete, gestoppte, gebundene, ungebundene oder unbekannte Zustände.", "Anzahlen je Laufzeitstatus"), ("BizTalk Event Log", "Aktuelle BizTalk-bezogene Fehler und Warnungen im Application Log.", "Errors und Warnings im Zeitfenster"), ), (4.5, 8.2, 4.5), font_size=8.0, ) add_heading(document, "Endpoint-Prüfung", level=2) add_body(document, "Die Erreichbarkeitsprüfung beschränkt sich auf aktuell gestartete Send Ports und aktivierte Receive Locations. Adressen werden auf Protokoll, Host und Port reduziert, dedupliziert und mit begrenzter Parallelität geprüft. Im gesunden Zustand erscheint nur eine Gesamtaussage; bei Fehlern werden ausschließlich die nicht erreichbaren Ziele begrenzt aufgelistet.") add_callout(document, "Wichtige Abgrenzung: ", "Die Endpoint-Prüfung ist ein technischer Netzwerkcheck. Sie bestätigt je nach Protokoll DNS, Route, Firewall und einen annehmenden TCP-Port beziehungsweise den lokalen UDP-Versand. Sie führt keine Anmeldung durch, sendet keine BizTalk-Nachricht und bewertet nicht die fachliche Funktion des Zielsystems.", fill=LIGHT_ORANGE, border=ORANGE) document.add_page_break() # 5: Data exchange add_heading(document, "5. Welche Daten werden ausgetauscht?") add_body(document, "Der Datenaustausch besteht aus drei klar getrennten Stufen. Verarbeitet werden Betriebs- und Konfigurationsmetadaten, keine fachlichen Nachrichteninhalte.") add_table( document, ("Stufe", "Dateninhalt", "Transport und Schutz"), ( ("1. Quellen → Collector", "BizTalk-Gruppe und Datenbankziele; Namen und Laufzeitzustände von Host Instances, suspendierten Instanzen, Receive Locations, Send Ports und Orchestrations; Event-Log-Zähler; Host/Port aktiver Endpunkte.", "Lokale WMI-Abfragen, integrierter SQL-Verbindungstest, lokales Event Log und begrenzte TCP-/UDP-Probes."), ("2. Collector → Snapshot", "Erzeugungszeit, Quellmaschine, Collector-Identität, Zeilenanzahl, SHA-256 und neun Checkmk-Zeilen mit Status, Metriken und Kurztext.", "Lokale Datei, atomar ersetzt, ACL-geschützt, standardmäßig maximal 1 MiB."), ("3. Consumer → Checkmk", "Validierte Local-Check-Zeilen: Status, Zähler, Summaries sowie bei Störungen begrenzte Objekt- oder Ziellisten.", "Standardausgabe des Local Checks als Bestandteil der Checkmk-Agentenausgabe."), ), (3.7, 8.2, 5.3), font_size=8.0, ) add_heading(document, "Beispiel des lokalen Snapshot-Vertrags", level=2) add_code_block( document, ( "BIZTALK_CHECKMK_PULSE_SNAPSHOT_V2", "generatedUtc=2026-08-10T08:15:00.0000000Z", "machineBase64=QlRaLVBSSC0wMQ==", "identityBase64=RE9NQUlOXHN2Y19iaXp0YWxrX21vbml0b3Jpbmc=", "payloadLines=9", "payloadSha256=<64 hexadezimale Zeichen>", "", '0 "BizTalk Suspended Instances" biztalk_suspended_total=0;;;0|... Suspended total=0.', '0 "BizTalk Endpoint Reachability" biztalk_endpoints_active=61;;;0|... Alle 61 aktiven Endpunkte sind erreichbar.', ), ) add_body(document, "Die Base64-Felder verhindern problematische Trennzeichen im Header; sie sind keine Verschlüsselung. SHA-256 erkennt unvollständige oder veränderte Snapshots. Der Schutz vor unberechtigtem lokalem Zugriff erfolgt über Windows-Dateirechte.") add_heading(document, "Enthaltene und nicht enthaltene Informationen", level=2) add_table( document, ("Im Monitoring enthalten", "Nicht erhoben oder übertragen"), ( ("Technische Status- und Mengeninformationen", "BizTalk-Nachrichten oder fachliche Payloads"), ("Relevante BizTalk-Anwendungs- und Artefaktnamen", "Passwörter, Tokens oder Endpoint-Zugangsdaten"), ("DB-Server/-Namen und Ausführungsidentität zur Diagnose", "Vollständige Inhalte der BizTalk-Datenbanken"), ("Bei Endpoint-Fehlern: Protokoll, Host und Port", "Anmeldungen oder fachliche Requests an Zielsysteme"), ("Fehlerkategorien und begrenzte Diagnosetexte", "Dateiinhalte von UNC-/SMB-Freigaben"), ), (8.6, 8.6), font_size=8.2, ) add_callout(document, "Dateneinordnung: ", "Die Ausgabe enthält technische Betriebsmetadaten. Artefakt-, Server- und Zielnamen können Rückschlüsse auf Integrationen zulassen und sollten innerhalb der bestehenden Zugriffs- und Aufbewahrungsregeln des Monitorings behandelt werden.") document.add_page_break() # 6–7: Operation and conclusion add_heading(document, "6. Betrieb, Sicherheit und Grenzen") add_heading(document, "Betriebsmodell", level=2) add_bullet(document, "Collector jede Minute; parallele Läufe werden verhindert.") add_bullet(document, "Endpoint-Katalog übernimmt relevante Kandidatenänderungen im nächsten Minutenlauf und wird spätestens wöchentlich vollständig abgeglichen.") add_bullet(document, "Nicht automatisch prüfbare Adapter bleiben als Abdeckungsmetriken sichtbar, ohne erfolgreiche Netzwerkprobes pauschal zu entwerten.") add_bullet(document, "Getrennte Tageslogs für Provider und Consumer; Standardaufbewahrung 30 Tage.") add_bullet(document, "Erwartet inaktive Receive Locations und Send Ports können exakt allowlisted werden und bleiben als Messwert sichtbar.") add_bullet(document, "Endpoint-Prüfung standardmäßig mit maximal 100 Zielen, 16 parallelen Probes und 3 Sekunden Timeout je Ziel.") add_bullet(document, "Der Installer validiert neue Versionen auf exakt neun Services, akzeptiert vollständige Legacy-Verträge mit weniger Services und stoppt entfernte oder umbenannte Servicenamen vor jeder Umschaltung.") add_bullet(document, "Ein laufbezogenes Setup-Log protokolliert Phase, Binary-Version, Self-Test-Ausgabe, Taskcodes, Exception-Kette und Rollback ohne Kennwort.") add_bullet(document, "Nach der Umschaltung erzwingt das Setup einen frischen Katalogabgleich. Kernservice-UNKNOWN bleibt blockierend; ein isoliertes Endpoint-Reachability-UNKNOWN bleibt als sichtbare Betriebswarnung erhalten und löst keinen Rollback aus.") add_heading(document, "Sicherheitsmodell", level=2) add_table( document, ("Kontrolle", "Wirkung"), ( ("Dediziertes Servicekonto", "BizTalk-/SQL-Zugriff ist auf den Collector begrenzt und wird nicht auf alle LocalSystem-Dienste ausgeweitet."), ("BizTalk Read-Only-Gruppe", "Lesende BizTalk-Rolle; individuelle SQL-Rechte oder SQL-sysadmin sind nicht vorgesehen."), ("ACL-getrennter Snapshot", "Collector schreibt, LocalSystem liest; Konfiguration und Logs werden getrennt behandelt."), ("Integritäts- und Altersprüfung", "Falsche Maschine, fehlerhaftes Format, abweichende Prüfsumme oder veraltete Daten führen zu UNKNOWN."), ("Begrenzte Ausgabe", "Listen und Diagnosen werden gekürzt; vollständige Zähler bleiben als Metriken erhalten."), ), (4.6, 12.6), font_size=8.3, ) add_heading(document, "Bewusste Grenzen", level=2) add_bullet(document, "Die Lösung ersetzt nicht das allgemeine Checkmk-MSSQL-Plugin und ist kein vollständiges SQL-Monitoring.") add_bullet(document, "Sie bewertet keine fachliche End-to-End-Verarbeitung einer BizTalk-Schnittstelle.") add_bullet(document, "Ein erreichbarer Port beweist nicht, dass Anmeldung, Protokoll oder Zielanwendung fachlich funktionieren.") add_bullet(document, "SHA-256 schützt die Übergabe vor unbemerkten Fehlern, nicht vor einem lokalen Administrator.") add_bullet(document, "Schwellwerte, Allowlist-Einträge und Benachrichtigungen bleiben Teil der betrieblichen Konfiguration.") add_heading(document, "7. Fazit") add_body(document, "BizTalk Checkmk Pulse schafft eine klar abgegrenzte Brücke zwischen BizTalk Server und Checkmk. Die Lösung liefert eine verständliche, alarmierbare Sicht auf die wichtigsten Laufzeitkomponenten, hält den Checkmk-Agenten von privilegierten Datenquellen fern und macht auch Fehler im Datentransport selbst sichtbar. Der ausgetauschte Datenumfang bleibt auf technische Betriebsmetadaten beschränkt.") add_callout(document, "Kernaussage: ", "Ein privilegierter Read-only-Collector sammelt, ein unprivilegierter Consumer validiert und übergibt – Checkmk erhält neun stabile Services statt direkten Zugriff auf BizTalk oder SQL.", fill=LIGHT_GREEN, border=GREEN) add_rich_paragraph( document, (("Dokumentbasis: Implementierung und Projektdokumentation von BizTalk Checkmk Pulse 2.2.6 sowie Checkmk User Guide „Local checks“, abgerufen am 10. August 2026.", {"size": 7.8, "color": MUTED}),), before=8, after=0, ) document.save(OUTPUT) print(OUTPUT) if __name__ == "__main__": build_document()