diff --git a/f/Backup/backup_restore_orchestrator__flow/flow_fehler_handler.lock b/f/Backup/backup_restore_orchestrator__flow/flow-fehler_per_nextcloud_talk_melden.lock similarity index 100% rename from f/Backup/backup_restore_orchestrator__flow/flow_fehler_handler.lock rename to f/Backup/backup_restore_orchestrator__flow/flow-fehler_per_nextcloud_talk_melden.lock diff --git a/f/Backup/backup_restore_orchestrator__flow/flow_fehler_handler.py b/f/Backup/backup_restore_orchestrator__flow/flow-fehler_per_nextcloud_talk_melden.py similarity index 100% rename from f/Backup/backup_restore_orchestrator__flow/flow_fehler_handler.py rename to f/Backup/backup_restore_orchestrator__flow/flow-fehler_per_nextcloud_talk_melden.py diff --git a/f/Backup/backup_restore_orchestrator__flow/flow.yaml b/f/Backup/backup_restore_orchestrator__flow/flow.yaml index eb014de..f781f6a 100644 --- a/f/Backup/backup_restore_orchestrator__flow/flow.yaml +++ b/f/Backup/backup_restore_orchestrator__flow/flow.yaml @@ -49,18 +49,19 @@ value: summary: SSH-Key aus DB testen value: type: rawscript - content: '!inline ssh_key_versuch.py' + content: '!inline ssh-key_aus_db_testen.py' input_transforms: prev: type: javascript expr: results.b - lock: '!inline ssh_key_versuch.lock' + lock: '!inline ssh-key_aus_db_testen.lock' language: python3 - id: g summary: SSH-Credentials fuer alle Restore-Server aus Bitwarden (Fallback) value: type: rawscript - content: '!inline ssh-credentials_fuer_alle_restore-server_aus_bitwarden.py' + content: '!inline + ssh-credentials_fuer_alle_restore-server_aus_bitwarden_(fallback).py' input_transforms: bw_url: type: static @@ -68,7 +69,8 @@ value: prev: type: javascript expr: results.i - lock: '!inline ssh-credentials_fuer_alle_restore-server_aus_bitwarden.lock' + lock: '!inline + ssh-credentials_fuer_alle_restore-server_aus_bitwarden_(fallback).lock' language: python3 - id: c summary: Script deployen & PBS-Datastores auf allen Servern registrieren @@ -130,7 +132,7 @@ value: summary: Flow-Fehler per Nextcloud Talk melden value: type: rawscript - content: '!inline flow_fehler_handler.py' + content: '!inline flow-fehler_per_nextcloud_talk_melden.py' input_transforms: error: type: javascript @@ -138,7 +140,7 @@ value: flow_input: type: javascript expr: flow_input - lock: '!inline flow_fehler_handler.lock' + lock: '!inline flow-fehler_per_nextcloud_talk_melden.lock' language: python3 schema: $schema: https://json-schema.org/draft/2020-12/schema diff --git a/f/Backup/backup_restore_orchestrator__flow/ssh-credentials_fuer_alle_restore-server_aus_bitwarden.lock b/f/Backup/backup_restore_orchestrator__flow/ssh-credentials_fuer_alle_restore-server_aus_bitwarden_(fallback).lock similarity index 100% rename from f/Backup/backup_restore_orchestrator__flow/ssh-credentials_fuer_alle_restore-server_aus_bitwarden.lock rename to f/Backup/backup_restore_orchestrator__flow/ssh-credentials_fuer_alle_restore-server_aus_bitwarden_(fallback).lock diff --git a/f/Backup/backup_restore_orchestrator__flow/ssh-credentials_fuer_alle_restore-server_aus_bitwarden.py b/f/Backup/backup_restore_orchestrator__flow/ssh-credentials_fuer_alle_restore-server_aus_bitwarden_(fallback).py similarity index 100% rename from f/Backup/backup_restore_orchestrator__flow/ssh-credentials_fuer_alle_restore-server_aus_bitwarden.py rename to f/Backup/backup_restore_orchestrator__flow/ssh-credentials_fuer_alle_restore-server_aus_bitwarden_(fallback).py diff --git a/f/Backup/backup_restore_orchestrator__flow/ssh_key_versuch.lock b/f/Backup/backup_restore_orchestrator__flow/ssh-key_aus_db_testen.lock similarity index 100% rename from f/Backup/backup_restore_orchestrator__flow/ssh_key_versuch.lock rename to f/Backup/backup_restore_orchestrator__flow/ssh-key_aus_db_testen.lock diff --git a/f/Backup/backup_restore_orchestrator__flow/ssh_key_versuch.py b/f/Backup/backup_restore_orchestrator__flow/ssh-key_aus_db_testen.py similarity index 100% rename from f/Backup/backup_restore_orchestrator__flow/ssh_key_versuch.py rename to f/Backup/backup_restore_orchestrator__flow/ssh-key_aus_db_testen.py diff --git a/f/Backup/backup_restore_orchestrator__flow/testpause.lock b/f/Backup/backup_restore_orchestrator__flow/testpause.lock deleted file mode 100644 index 48ff042..0000000 --- a/f/Backup/backup_restore_orchestrator__flow/testpause.lock +++ /dev/null @@ -1,17 +0,0 @@ -# py: 3.12 -anyio==4.12.1 -bcrypt==5.0.0 -certifi==2026.2.25 -cffi==2.0.0 -cryptography==46.0.5 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.28.1 -idna==3.11 -invoke==2.2.1 -mysql-connector-python==9.6.0 -paramiko==4.0.0 -pycparser==3.0 -pynacl==1.6.2 -typing-extensions==4.15.0 -wmill==1.657.2 diff --git a/f/Backup/backup_restore_orchestrator__flow/testpause.py b/f/Backup/backup_restore_orchestrator__flow/testpause.py deleted file mode 100644 index 704e31f..0000000 --- a/f/Backup/backup_restore_orchestrator__flow/testpause.py +++ /dev/null @@ -1,23 +0,0 @@ -def main(prev: dict): - print("=" * 60) - print("TESTPAUSE — Flow stoppt hier. Ergebnisse:") - print("=" * 60) - - server_creds = prev.get("server_creds", {}) - needs_bitwarden = prev.get("needs_bitwarden", []) - servers = prev.get("target_servers", []) - - print(f"\nServer gesamt: {[s['hostname'] for s in servers]}") - print(f"needs_bitwarden: {needs_bitwarden}") - print(f"\nAuthentifizierung:") - for hostname, creds in server_creds.items(): - method = creds.get("auth_method", "password") - user = creds.get("username", "?") - print(f" {hostname}: {method} ({user})") - - for hostname in needs_bitwarden: - if hostname not in server_creds: - print(f" {hostname}: FEHLT — weder Key noch Bitwarden!") - - print("=" * 60) - return prev diff --git a/f/Proxmox/folder.meta.yaml b/f/Proxmox/folder.meta.yaml index 5582833..9aaaf9b 100644 --- a/f/Proxmox/folder.meta.yaml +++ b/f/Proxmox/folder.meta.yaml @@ -1,2 +1,6 @@ summary: Proxmox -description: '' +display_name: Proxmox +extra_perms: + sebastianserfling@stines.de: true +owners: + - sebastianserfling@stines.de diff --git a/f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen.lock b/f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen.lock new file mode 100644 index 0000000..e75571f --- /dev/null +++ b/f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen.lock @@ -0,0 +1,44 @@ +{ + "dependencies": { + "mysql2": "latest" + } +} +//bun.lock +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "dependencies": { + "mysql2": "latest", + }, + }, + }, + "packages": { + "@types/node": ["@types/node@25.5.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg=="], + + "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], + + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + + "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], + + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + + "is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="], + + "mysql2": ["mysql2@3.21.0", "", { "dependencies": { "aws-ssl-profiles": "^1.1.2", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.2", "long": "^5.3.2", "lru.min": "^1.1.4", "named-placeholders": "^1.1.6", "sql-escaper": "^1.3.3" }, "peerDependencies": { "@types/node": ">= 8" } }, "sha512-CYNKIuhnalXHTa4gonZ+KhzLESKllvo1qQIDYUVuatpN4NgMk+lsA3WwHYno5AS4PACUiD2qEmiVD9pr3bXWOw=="], + + "named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "sql-escaper": ["sql-escaper@1.3.3", "", {}, "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw=="], + + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + } +} diff --git a/f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen.script.lock b/f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen.script.lock new file mode 100644 index 0000000..7a2c26d --- /dev/null +++ b/f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen.script.lock @@ -0,0 +1,44 @@ +{ + "dependencies": { + "mysql2": "latest" + } +} +//bun.lock +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "dependencies": { + "mysql2": "latest", + }, + }, + }, + "packages": { + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], + + "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], + + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + + "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], + + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + + "is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="], + + "mysql2": ["mysql2@3.22.3", "", { "dependencies": { "aws-ssl-profiles": "^1.1.2", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.2", "long": "^5.3.2", "lru.min": "^1.1.4", "named-placeholders": "^1.1.6", "sql-escaper": "^1.3.3" }, "peerDependencies": { "@types/node": ">= 8" } }, "sha512-uWWxvZSRvRhtBdh2CdcuK83YcOfPdmEeEYB069bAmPnV93QApDGVPuvCQOLjlh7tYHEWdgQPrn6kosDxHBVLkA=="], + + "named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "sql-escaper": ["sql-escaper@1.3.3", "", {}, "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw=="], + + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + } +} diff --git a/f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen.script.yaml b/f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen.script.yaml new file mode 100644 index 0000000..23c5a94 --- /dev/null +++ b/f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen.script.yaml @@ -0,0 +1,59 @@ +summary: '' +description: '' +lock: '!inline + f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen.script.lock' +kind: script +schema: + $schema: https://json-schema.org/draft/2020-12/schema + type: object + properties: + database: + type: object + description: '' + default: null + format: resource-mysql + properties: + database: + type: string + description: '' + originalType: string + host: + type: string + description: '' + originalType: string + password: + type: string + description: '' + originalType: string + port: + type: number + description: '' + user: + type: string + description: '' + originalType: string + record: + type: object + description: '' + default: null + format: resource-login_record + properties: + ipaddress: + type: string + description: '' + originalType: string + lastlogon: + type: string + description: '' + originalType: string + memberof: + type: string + description: '' + originalType: string + username: + type: string + description: '' + originalType: string + required: + - database + - record diff --git a/f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen.ts b/f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen.ts new file mode 100644 index 0000000..a13245f --- /dev/null +++ b/f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen.ts @@ -0,0 +1,39 @@ +type Mysql = { + host: string; + port: number; + user: string; + password: string; + database: string; +}; + +type LoginRecord = { + username: string; + lastaccess: string; + ipaddress: string; + memberof: string; +}; + +export async function main( + database: Mysql, + record: LoginRecord +): Promise<{ inserted: boolean }> { + const mysql2 = await import("mysql2/promise"); + + const conn = await mysql2.createConnection({ + host: database.host, + port: database.port, + user: database.user, + password: database.password, + database: database.database, + }); + + try { + await conn.execute( + "INSERT INTO `bronze.services.reporting` (username, lastaccess, ipaddress, add_date, memberof) VALUES (?, ?, ?, NOW(), ?)", + [record.username, record.lastaccess, record.ipaddress, record.memberof] + ); + return { inserted: true }; + } finally { + await conn.end(); + } +} diff --git a/f/Reporting/exchange_logins__flow/ex-server_mit_rport.io_clients_abgleichen.lock b/f/Reporting/exchange_logins__flow/ex-server_mit_rport.io_clients_abgleichen.lock new file mode 100644 index 0000000..e75571f --- /dev/null +++ b/f/Reporting/exchange_logins__flow/ex-server_mit_rport.io_clients_abgleichen.lock @@ -0,0 +1,44 @@ +{ + "dependencies": { + "mysql2": "latest" + } +} +//bun.lock +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "dependencies": { + "mysql2": "latest", + }, + }, + }, + "packages": { + "@types/node": ["@types/node@25.5.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg=="], + + "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], + + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + + "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], + + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + + "is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="], + + "mysql2": ["mysql2@3.21.0", "", { "dependencies": { "aws-ssl-profiles": "^1.1.2", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.2", "long": "^5.3.2", "lru.min": "^1.1.4", "named-placeholders": "^1.1.6", "sql-escaper": "^1.3.3" }, "peerDependencies": { "@types/node": ">= 8" } }, "sha512-CYNKIuhnalXHTa4gonZ+KhzLESKllvo1qQIDYUVuatpN4NgMk+lsA3WwHYno5AS4PACUiD2qEmiVD9pr3bXWOw=="], + + "named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "sql-escaper": ["sql-escaper@1.3.3", "", {}, "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw=="], + + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + } +} diff --git a/f/Reporting/exchange_logins__flow/ex-server_mit_rport.io_clients_abgleichen.ts b/f/Reporting/exchange_logins__flow/ex-server_mit_rport.io_clients_abgleichen.ts new file mode 100644 index 0000000..c3455d4 --- /dev/null +++ b/f/Reporting/exchange_logins__flow/ex-server_mit_rport.io_clients_abgleichen.ts @@ -0,0 +1,88 @@ +type Mysql = { + host: string; + port: number; + user: string; + password: string; + database: string; +}; + +type RportClient = { + rport_client_id: string; + hostname: string; + ipaddress: string; +}; + +export async function main( + database: Mysql, + rportio_base_url: string, + rportio_username: string, + rportio_api_token: string +): Promise { + const mysql2 = await import("mysql2/promise"); + + // 1. Query MySQL for all active Exchange servers + const conn = await mysql2.createConnection({ + host: database.host, + port: database.port, + user: database.user, + password: database.password, + database: database.database, + }); + + const [rows] = await conn.execute( + "SELECT hostname, privat_ipaddress FROM `bronze.server` WHERE services LIKE '%EX%' AND (disable_date IS NULL OR disable_date > NOW())" + ); + await conn.end(); + + const dbServers = rows as Array<{ hostname: string; privat_ipaddress: string }>; + if (dbServers.length === 0) return []; + + // Build lookup maps for fast matching (hostname → DB row) + const byHostname = new Map(dbServers.map((s) => [s.hostname.toLowerCase(), s])); + const byIp = new Map(dbServers.map((s) => [s.privat_ipaddress, s])); + + // 2. Query rport.io for all connected clients + const auth = Buffer.from(`${rportio_username}:${rportio_api_token}`).toString("base64"); + const headers = { + Authorization: `Basic ${auth}`, + "Content-Type": "application/json", + }; + + // @ts-ignore - Bun-specific TLS option to allow self-signed certificates + const resp = await fetch( + `${rportio_base_url}/api/v1/clients?filter[connection_state]=connected&fields[clients]=id,name,hostname,ipv4&page[limit]=500`, + { headers, tls: { rejectUnauthorized: false } } + ); + + if (!resp.ok) { + const err = await resp.text(); + throw new Error(`rport.io clients list failed [${resp.status}]: ${err}`); + } + + const data = await resp.json(); + const clients = data?.data ?? []; + + // 3. Match rport.io clients against DB server list (hostname or IP) + const matched: RportClient[] = []; + for (const client of clients) { + const rportHostname = (client.hostname ?? "").toLowerCase(); + const rportIps: string[] = client.ipv4 ?? []; + + const dbRow = byHostname.get(rportHostname) + ?? rportIps.map((ip) => byIp.get(ip)).find(Boolean); + + if (dbRow) { + matched.push({ + rport_client_id: client.id, + hostname: client.hostname ?? client.name, + ipaddress: dbRow.privat_ipaddress, + }); + } + } + + console.log( + `Found ${dbServers.length} EX servers in DB, ${clients.length} connected rport.io clients, ${matched.length} matched` + ); + + return matched; +} diff --git a/f/Reporting/exchange_logins__flow/ex-server_mit_rport.script.lock b/f/Reporting/exchange_logins__flow/ex-server_mit_rport.script.lock new file mode 100644 index 0000000..7a2c26d --- /dev/null +++ b/f/Reporting/exchange_logins__flow/ex-server_mit_rport.script.lock @@ -0,0 +1,44 @@ +{ + "dependencies": { + "mysql2": "latest" + } +} +//bun.lock +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "dependencies": { + "mysql2": "latest", + }, + }, + }, + "packages": { + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], + + "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], + + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + + "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], + + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + + "is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="], + + "mysql2": ["mysql2@3.22.3", "", { "dependencies": { "aws-ssl-profiles": "^1.1.2", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.2", "long": "^5.3.2", "lru.min": "^1.1.4", "named-placeholders": "^1.1.6", "sql-escaper": "^1.3.3" }, "peerDependencies": { "@types/node": ">= 8" } }, "sha512-uWWxvZSRvRhtBdh2CdcuK83YcOfPdmEeEYB069bAmPnV93QApDGVPuvCQOLjlh7tYHEWdgQPrn6kosDxHBVLkA=="], + + "named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "sql-escaper": ["sql-escaper@1.3.3", "", {}, "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw=="], + + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + } +} diff --git a/f/Reporting/exchange_logins__flow/ex-server_mit_rport.script.yaml b/f/Reporting/exchange_logins__flow/ex-server_mit_rport.script.yaml new file mode 100644 index 0000000..f7504b1 --- /dev/null +++ b/f/Reporting/exchange_logins__flow/ex-server_mit_rport.script.yaml @@ -0,0 +1,53 @@ +summary: '' +description: '' +lock: '!inline f/Reporting/exchange_logins__flow/ex-server_mit_rport.script.lock' +kind: script +schema: + $schema: https://json-schema.org/draft/2020-12/schema + type: object + properties: + database: + type: object + description: '' + default: null + format: resource-mysql + properties: + database: + type: string + description: '' + originalType: string + host: + type: string + description: '' + originalType: string + password: + type: string + description: '' + originalType: string + port: + type: number + description: '' + user: + type: string + description: '' + originalType: string + rportio_api_token: + type: string + description: '' + default: null + originalType: string + rportio_base_url: + type: string + description: '' + default: null + originalType: string + rportio_username: + type: string + description: '' + default: null + originalType: string + required: + - database + - rportio_base_url + - rportio_username + - rportio_api_token diff --git a/f/Reporting/exchange_logins__flow/flow.yaml b/f/Reporting/exchange_logins__flow/flow.yaml new file mode 100644 index 0000000..6916b9d --- /dev/null +++ b/f/Reporting/exchange_logins__flow/flow.yaml @@ -0,0 +1,91 @@ +summary: Exchange Logins Collector +description: > + Lädt alle aktiven EX-Server aus bronze.server, gleicht sie mit den + verbundenen rport.io Clients ab (per Hostname oder IP), fragt auf jedem + Server per PowerShell die AD-Gruppe G-Exchange-User ab (username ohne + E-Mail, lastlogon, ipaddress vom Server) und schreibt die Ergebnisse in + bronze.services.reporting. +value: + modules: + - id: find_ex_clients + summary: EX-Server mit rport.io Clients abgleichen + value: + type: rawscript + content: '!inline ex-server_mit_rport.io_clients_abgleichen.ts' + input_transforms: + database: + type: static + value: $res:u/sebastianserfling/fascinating_mysql + rportio_api_token: + type: static + value: $var:f/Reporting/rportio_api_token + rportio_base_url: + type: static + value: $var:f/Reporting/rportio_base_url + rportio_username: + type: static + value: $var:f/Reporting/rportio_username + lock: '!inline ex-server_mit_rport.io_clients_abgleichen.lock' + language: bun + - id: process_servers + summary: Pro Server Logins sammeln und speichern + value: + type: forloopflow + modules: + - id: execute_ps + summary: PowerShell via rport.io ausführen + value: + type: rawscript + content: '!inline powershell_via_rport.io_ausführen.ts' + input_transforms: + client_id: + type: javascript + expr: flow_input.iter.value.rport_client_id + rportio_api_token: + type: static + value: $var:f/Reporting/rportio_api_token + rportio_base_url: + type: static + value: $var:f/Reporting/rportio_base_url + rportio_username: + type: static + value: $var:f/Reporting/rportio_username + server_ip: + type: javascript + expr: flow_input.iter.value.ipaddress + lock: '!inline powershell_via_rport.io_ausführen.lock' + language: bun + - id: insert_logins + summary: Login-Einträge in MySQL speichern + value: + type: forloopflow + modules: + - id: insert_login + summary: Einzelnen Login-Eintrag einfügen + value: + type: rawscript + content: '!inline einzelnen_login-eintrag_einfügen.ts' + input_transforms: + database: + type: static + value: $res:u/sebastianserfling/fascinating_mysql + record: + type: javascript + expr: flow_input.iter.value + lock: '!inline einzelnen_login-eintrag_einfügen.lock' + language: bun + iterator: + type: javascript + expr: results.execute_ps + parallel: false + skip_failures: false + iterator: + type: javascript + expr: results.find_ex_clients + parallel: false + skip_failures: true +schema: + $schema: https://json-schema.org/draft/2019-09/schema + type: object + properties: {} + required: [] diff --git a/f/Reporting/exchange_logins__flow/powershell_via_rport.io_ausführen.lock b/f/Reporting/exchange_logins__flow/powershell_via_rport.io_ausführen.lock new file mode 100644 index 0000000..c24eb1c --- /dev/null +++ b/f/Reporting/exchange_logins__flow/powershell_via_rport.io_ausführen.lock @@ -0,0 +1,5 @@ +{ + "dependencies": {} +} +//bun.lock + \ No newline at end of file diff --git a/f/Reporting/exchange_logins__flow/powershell_via_rport.io_ausführen.ts b/f/Reporting/exchange_logins__flow/powershell_via_rport.io_ausführen.ts new file mode 100644 index 0000000..7cdf427 --- /dev/null +++ b/f/Reporting/exchange_logins__flow/powershell_via_rport.io_ausführen.ts @@ -0,0 +1,119 @@ +type LoginRecord = { + username: string; + lastaccess: string; + ipaddress: string; + memberof: string; +}; + +export async function main( + rportio_base_url: string, + rportio_username: string, + rportio_api_token: string, + client_id: string, + server_ip: string +): Promise { + const psScript = ` +$ErrorActionPreference = 'SilentlyContinue' +$groupName = 'G-Exchange-User' +$serverIp = '${server_ip}' +$firstOfMonth = (Get-Date -Day 1).ToString('yyyy-MM-dd 00:00:00') + +Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn -ErrorAction SilentlyContinue + +$members = @() +try { + $members = Get-ADGroupMember -Identity $groupName -Recursive -ErrorAction Stop | + Where-Object { $_.objectClass -eq 'user' } +} catch {} + +$result = [System.Collections.Generic.List[object]]::new() +foreach ($m in $members) { + $mailbox = $null + try { + $mailbox = Get-Mailbox -Identity $m.SamAccountName -RecipientTypeDetails UserMailbox -ErrorAction Stop + } catch { continue } + + $lastaccess = $firstOfMonth + try { + $stats = Get-MailboxStatistics -Identity $mailbox.Identity -ErrorAction Stop + if ($stats.LastLogonTime) { + $lastaccess = $stats.LastLogonTime.ToString('yyyy-MM-dd HH:mm:ss') + } + } catch {} + + $result.Add([PSCustomObject]@{ + username = $mailbox.SamAccountName + lastaccess = $lastaccess + ipaddress = $serverIp + memberof = $groupName + }) +} +if ($result.Count -eq 0) { Write-Output '[]' } else { $result | ConvertTo-Json -Depth 3 -Compress } +`.trim(); + + // rport.io powershell interpreter executes the script directly as PS code + const command = psScript; + + const auth = Buffer.from(`${rportio_username}:${rportio_api_token}`).toString("base64"); + const headers: Record = { + Authorization: `Basic ${auth}`, + "Content-Type": "application/json", + }; + + const tlsOpts = { tls: { rejectUnauthorized: false } }; + + // Submit command to rport.io + // @ts-ignore - Bun-specific TLS option for self-signed certificates + const execResp = await fetch( + `${rportio_base_url}/api/v1/clients/${client_id}/commands`, + { + method: "POST", + headers, + body: JSON.stringify({ command, interpreter: "powershell", timeout_sec: 120 }), + ...tlsOpts, + } + ); + if (!execResp.ok) { + const text = await execResp.text(); + throw new Error(`rport.io execute failed [${execResp.status}]: ${text}`); + } + const execData = await execResp.json(); + const jid: string = execData?.data?.jid; + if (!jid) throw new Error(`No job ID from rport.io: ${JSON.stringify(execData)}`); + + // Poll until finished (max 120s) + let cmdResult: Record | null = null; + for (let i = 0; i < 60; i++) { + await new Promise((r) => setTimeout(r, 2000)); + // @ts-ignore - Bun-specific TLS option + const statusResp = await fetch( + `${rportio_base_url}/api/v1/clients/${client_id}/commands/${jid}`, + { headers, ...tlsOpts } + ); + if (!statusResp.ok) continue; + const statusData = await statusResp.json(); + const cmd = statusData?.data as Record; + if (cmd?.finished_at) { + cmdResult = cmd; + break; + } + } + if (!cmdResult) throw new Error("Timeout waiting for rport.io command result"); + + const status = cmdResult.status as string; + if (status === "failed" || status === "unknown") { + const result = cmdResult.result as Record ?? {}; + throw new Error(`PowerShell failed [${status}]: ${cmdResult.error ?? result.stderr ?? ""}`); + } + + const result = cmdResult.result as Record ?? {}; + const stdout = (result.stdout ?? "").trim(); + if (!stdout || stdout === "[]") return []; + + try { + const parsed = JSON.parse(stdout); + return Array.isArray(parsed) ? parsed : [parsed]; + } catch { + throw new Error(`Failed to parse PowerShell JSON output: ${stdout}`); + } +} diff --git a/f/Reporting/exchange_logins__flow/powershell_via_rport.script.lock b/f/Reporting/exchange_logins__flow/powershell_via_rport.script.lock new file mode 100644 index 0000000..c24eb1c --- /dev/null +++ b/f/Reporting/exchange_logins__flow/powershell_via_rport.script.lock @@ -0,0 +1,5 @@ +{ + "dependencies": {} +} +//bun.lock + \ No newline at end of file diff --git a/f/Reporting/exchange_logins__flow/powershell_via_rport.script.yaml b/f/Reporting/exchange_logins__flow/powershell_via_rport.script.yaml new file mode 100644 index 0000000..3ee407b --- /dev/null +++ b/f/Reporting/exchange_logins__flow/powershell_via_rport.script.yaml @@ -0,0 +1,39 @@ +summary: '' +description: '' +lock: '!inline f/Reporting/exchange_logins__flow/powershell_via_rport.script.lock' +kind: script +schema: + $schema: https://json-schema.org/draft/2020-12/schema + type: object + properties: + client_id: + type: string + description: '' + default: null + originalType: string + rportio_api_token: + type: string + description: '' + default: null + originalType: string + rportio_base_url: + type: string + description: '' + default: null + originalType: string + rportio_username: + type: string + description: '' + default: null + originalType: string + server_ip: + type: string + description: '' + default: null + originalType: string + required: + - rportio_base_url + - rportio_username + - rportio_api_token + - client_id + - server_ip diff --git a/f/Reporting/run_sql_events__flow/erstellt_betreff_und_body_mit_allen_ergebnissen_der_calls.lock b/f/Reporting/run_sql_events__flow/erstellt_betreff_und_body_mit_allen_ergebnissen_der_calls.lock new file mode 100644 index 0000000..c24eb1c --- /dev/null +++ b/f/Reporting/run_sql_events__flow/erstellt_betreff_und_body_mit_allen_ergebnissen_der_calls.lock @@ -0,0 +1,5 @@ +{ + "dependencies": {} +} +//bun.lock + \ No newline at end of file diff --git a/f/Reporting/run_sql_events__flow/erstellt_betreff_und_body_mit_allen_ergebnissen_der_calls.ts b/f/Reporting/run_sql_events__flow/erstellt_betreff_und_body_mit_allen_ergebnissen_der_calls.ts new file mode 100644 index 0000000..19ffa96 --- /dev/null +++ b/f/Reporting/run_sql_events__flow/erstellt_betreff_und_body_mit_allen_ergebnissen_der_calls.ts @@ -0,0 +1,30 @@ +export async function main(results_array: any[], event_name: string = 'all_create', subject_prefix: string = '') { + const total = Array.isArray(results_array) ? results_array.length : 0 + const failed = Array.isArray(results_array) ? results_array.filter((r: any) => r && r.success === false).length : 0 + const succeeded = total - failed + const subject = `${subject_prefix ? subject_prefix + ' ' : ''}Ergebnisse für Event ${event_name}: ${succeeded}/${total} erfolgreich` + const lines: string[] = [] + lines.push(`Event: ${event_name}`) + lines.push(`Erfolgreich: ${succeeded}/${total}`) + lines.push('') + for (let i = 0; i < total; i++) { + const r: any = results_array[i] + if (!r) continue + lines.push(`CALL ${i + 1}: ${r.call}`) + if (r.success) { + let snippet = '' + try { + snippet = JSON.stringify(r.result) + if (snippet.length > 1000) snippet = snippet.slice(0, 1000) + '...' + } catch { snippet = '[unserialisierbar]' } + lines.push(' Status: OK') + lines.push(` Result: ${snippet}`) + } else { + lines.push(' Status: FEHLER') + lines.push(` Error: ${r.error}`) + } + lines.push('') + } + const body = lines.join('\n') + return { subject, body } +} \ No newline at end of file diff --git a/f/Reporting/run_sql_events__flow/extrahiert_alle_call-statements_aus_dem_event_definition-body.lock b/f/Reporting/run_sql_events__flow/extrahiert_alle_call-statements_aus_dem_event_definition-body.lock new file mode 100644 index 0000000..c24eb1c --- /dev/null +++ b/f/Reporting/run_sql_events__flow/extrahiert_alle_call-statements_aus_dem_event_definition-body.lock @@ -0,0 +1,5 @@ +{ + "dependencies": {} +} +//bun.lock + \ No newline at end of file diff --git a/f/Reporting/run_sql_events__flow/extrahiert_alle_call-statements_aus_dem_event_definition-body.ts b/f/Reporting/run_sql_events__flow/extrahiert_alle_call-statements_aus_dem_event_definition-body.ts new file mode 100644 index 0000000..52e21eb --- /dev/null +++ b/f/Reporting/run_sql_events__flow/extrahiert_alle_call-statements_aus_dem_event_definition-body.ts @@ -0,0 +1,18 @@ +export async function main(event_definition: string) { + if (!event_definition || typeof event_definition !== 'string') return [] + // Some MySQL store event body as full CREATE EVENT ... DO ...; we only need inside DO ... + let def = event_definition + const doMatch = def.match(/\bDO\s+(BEGIN[\s\S]*END|[^;]+);?/i) + if (doMatch) { + def = doMatch[1] + } + let body = def + .replace(/DELIMITER\s+[^\n]+/gi, ' ') + .replace(/\bBEGIN\b/gi, ' ') + .replace(/\bEND\b/gi, ' ') + .replace(/\s+/g, ' ') + + const regex = /CALL\s+[^;]+;/gi + const matches = body.match(regex) || [] + return matches.map((s) => s.trim()) +} \ No newline at end of file diff --git a/f/Reporting/run_sql_events__flow/extrahiert_event_definition-string_aus_den_zeilen.lock b/f/Reporting/run_sql_events__flow/extrahiert_event_definition-string_aus_den_zeilen.lock new file mode 100644 index 0000000..c24eb1c --- /dev/null +++ b/f/Reporting/run_sql_events__flow/extrahiert_event_definition-string_aus_den_zeilen.lock @@ -0,0 +1,5 @@ +{ + "dependencies": {} +} +//bun.lock + \ No newline at end of file diff --git a/f/Reporting/run_sql_events__flow/extrahiert_event_definition-string_aus_den_zeilen.ts b/f/Reporting/run_sql_events__flow/extrahiert_event_definition-string_aus_den_zeilen.ts new file mode 100644 index 0000000..eaac47f --- /dev/null +++ b/f/Reporting/run_sql_events__flow/extrahiert_event_definition-string_aus_den_zeilen.ts @@ -0,0 +1,21 @@ +export async function main(rows: any) { + // Normalize possible hub script return shapes + let arr: any[] = [] + if (Array.isArray(rows)) arr = rows + else if (rows && Array.isArray(rows.rows)) arr = rows.rows + else if (rows && Array.isArray(rows.result)) arr = rows.result + else if (rows && Array.isArray(rows.data)) arr = rows.data + + let def = '' + try { + if (arr.length > 0) { + const r = arr[0] + def = r.EVENT_DEFINITION || r.event_definition || '' + if (!def && r.EVENT_BODY) { + // Some MySQL variants expose body differently; just in case + def = r.EVENT_BODY + } + } + } catch {} + return { event_definition: def, row_count: arr.length } +} \ No newline at end of file diff --git a/f/Reporting/run_sql_events__flow/flow.yaml b/f/Reporting/run_sql_events__flow/flow.yaml new file mode 100644 index 0000000..b722cfd --- /dev/null +++ b/f/Reporting/run_sql_events__flow/flow.yaml @@ -0,0 +1,105 @@ +summary: Run SQL Events for Reporting +description: '' +value: + modules: + - id: get_event_def_rows + summary: Liest EVENT_DEFINITION für das MySQL-Event (Zeilen vom Hubskript) + value: + type: script + input_transforms: + mysql_conn: + type: static + value: $res:u/sebastianserfling/fascinating_mysql + query: + type: javascript + expr: "`SELECT EVENT_DEFINITION FROM information_schema.EVENTS WHERE EVENT_NAME + = '${flow_input.event_name ?? 'all_create'}' ORDER BY + (EVENT_SCHEMA = DATABASE()) DESC LIMIT 1`" + path: hub/17540/mysql/execute_query + - id: extract_event_def + summary: Extrahiert EVENT_DEFINITION-String aus den Zeilen + value: + type: rawscript + content: '!inline extrahiert_event_definition-string_aus_den_zeilen.ts' + input_transforms: + rows: + type: javascript + expr: results.get_event_def_rows + lock: '!inline extrahiert_event_definition-string_aus_den_zeilen.lock' + language: bun + - id: parse_calls + summary: Extrahiert alle CALL-Statements aus dem EVENT_DEFINITION-Body + value: + type: rawscript + content: '!inline + extrahiert_alle_call-statements_aus_dem_event_definition-body.ts' + input_transforms: + event_definition: + type: javascript + expr: results.extract_event_def.event_definition + lock: '!inline + extrahiert_alle_call-statements_aus_dem_event_definition-body.lock' + language: bun + - id: loop_calls + summary: Führt jede CALL einzeln aus und sendet bei Fehler Pushover + value: + type: forloopflow + modules: + - id: run_call + summary: Führt eine einzelne CALL aus, sendet bei Fehler Pushover und liefert + Ergebnis + value: + type: rawscript + content: '!inline + führt_eine_einzelne_call_aus,_sendet_bei_fehler_pushover_und_liefert_ergebnis.ts' + input_transforms: + call_sql: + type: javascript + expr: flow_input.iter.value + database_resource_path: + type: static + value: $res:u/sebastianserfling/fascinating_mysql + pushover_token: + type: javascript + expr: flow_input.pushover_token + pushover_user: + type: javascript + expr: flow_input.pushover_user + lock: '!inline + führt_eine_einzelne_call_aus,_sendet_bei_fehler_pushover_und_liefert_ergebnis.lock' + language: bun + iterator: + type: javascript + expr: results.parse_calls || [] + parallel: false + skip_failures: true + squash: false + - id: compose_email + summary: Erstellt Betreff und Body mit allen Ergebnissen der CALLs + value: + type: rawscript + content: '!inline erstellt_betreff_und_body_mit_allen_ergebnissen_der_calls.ts' + input_transforms: + event_name: + type: javascript + expr: flow_input.event_name ?? 'all_create' + results_array: + type: javascript + expr: results.loop_calls + subject_prefix: + type: javascript + expr: flow_input.email_subject_prefix ?? '' + lock: '!inline erstellt_betreff_und_body_mit_allen_ergebnissen_der_calls.lock' + language: bun + groups: [] +schema: + $schema: https://json-schema.org/draft/2020-12/schema + type: object + order: + - event_name + properties: + event_name: + type: string + description: Name des MySQL Events + default: all_create + required: [] diff --git a/f/Reporting/run_sql_events__flow/führt_eine_einzelne_call_aus,_sendet_bei_fehler_pushover_und_liefert_ergebnis.lock b/f/Reporting/run_sql_events__flow/führt_eine_einzelne_call_aus,_sendet_bei_fehler_pushover_und_liefert_ergebnis.lock new file mode 100644 index 0000000..ca14ebd --- /dev/null +++ b/f/Reporting/run_sql_events__flow/führt_eine_einzelne_call_aus,_sendet_bei_fehler_pushover_und_liefert_ergebnis.lock @@ -0,0 +1,19 @@ +{ + "dependencies": { + "windmill-client": "latest" + } +} +//bun.lock +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "dependencies": { + "windmill-client": "latest", + }, + }, + }, + "packages": { + "windmill-client": ["windmill-client@1.614.0", "", {}, "sha512-skpKI9mJXhUdwc9gZbounVclw22HTpXlhCAkGnkEAqys3xkRzA7v8/NY2Xt2WQrWV7GIN4ZGkOAgv2rk1r36hg=="], + } +} diff --git a/f/Reporting/run_sql_events__flow/führt_eine_einzelne_call_aus,_sendet_bei_fehler_pushover_und_liefert_ergebnis.ts b/f/Reporting/run_sql_events__flow/führt_eine_einzelne_call_aus,_sendet_bei_fehler_pushover_und_liefert_ergebnis.ts new file mode 100644 index 0000000..17c84a7 --- /dev/null +++ b/f/Reporting/run_sql_events__flow/führt_eine_einzelne_call_aus,_sendet_bei_fehler_pushover_und_liefert_ergebnis.ts @@ -0,0 +1,34 @@ +import * as wmill from 'windmill-client' + +export async function main(database_resource_path: string, call_sql: string, pushover_token?: string, pushover_user?: string) { + let success = false + let result: any = null + let errorMsg = '' + try { + // Pass the resource path string directly so the hub script can resolve it itself + const rows = await wmill.runScriptByPath('hub/17540/mysql/execute_query', { + mysql_conn: database_resource_path, + query: call_sql + }) + success = true + result = rows + } catch (err: any) { + errorMsg = err?.message || String(err) + if (pushover_token && pushover_user) { + try { + const body = new URLSearchParams({ + token: String(pushover_token), + user: String(pushover_user), + title: 'MySQL CALL fehlgeschlagen', + message: `Fehler bei CALL: ${call_sql}\nFehlermeldung: ${errorMsg}` + }) + await fetch('https://api.pushover.net/1/messages.json', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body + }) + } catch {} + } + } + return { call: call_sql, success, result, error: success ? null : errorMsg } +} \ No newline at end of file diff --git a/f/mail_to_talk/mail_to_talk__flow/flow.yaml b/f/mail_to_talk/mail_to_talk__flow/flow.yaml index 46b231c..8bb70a7 100644 --- a/f/mail_to_talk/mail_to_talk__flow/flow.yaml +++ b/f/mail_to_talk/mail_to_talk__flow/flow.yaml @@ -1,73 +1,49 @@ summary: E-Mails zu Nextcloud Talk description: > - Liest ungelesene E-Mails von zwei IMAP-Konten und sendet Benachrichtigungen - an einen Nextcloud Talk Raum. E-Mails werden als gelesen markiert. -schema: - $schema: "http://json-schema.org/draft-07/schema" - type: object - properties: - imap_config_1: - type: object - format: resource-imap - description: "IMAP Konto 1 (Ressource: f/mail_to_talk/imap_config)" - imap_config_2: - type: object - format: resource-imap - description: "IMAP Konto 2 (Ressource: f/mail_to_talk/imap_config_2)" - nextcloud_config: - type: object - format: resource-nextcloud - description: "Nextcloud Zugangsdaten (Ressource: f/mail_to_talk/nextcloud_talk_config)" - required: - - imap_config_1 - - imap_config_2 - - nextcloud_config + Liest ungelesene E-Mails von zwei IMAP-Konten und sendet Benachrichtigungen an + einen Nextcloud Talk Raum. E-Mails werden als gelesen markiert. value: modules: - id: fetch_emails_1 summary: Ungelesene E-Mails Konto 1 abrufen value: type: rawscript - language: python3 - content: "!inline fetch_emails.py" + content: '!inline ungelesene_e-mails_konto_1_abrufen.py' input_transforms: imap_config: type: javascript expr: flow_input.imap_config_1 + lock: '!inline ungelesene_e-mails_konto_1_abrufen.lock' + language: python3 stop_after_if: - expr: "false" + error_message: null + expr: 'false' skip_if_stopped: false - - id: fetch_emails_2 summary: Ungelesene E-Mails Konto 2 abrufen value: type: rawscript - language: python3 - content: "!inline fetch_emails.py" + content: '!inline ungelesene_e-mails_konto_2_abrufen.py' input_transforms: imap_config: type: javascript expr: flow_input.imap_config_2 + lock: '!inline ungelesene_e-mails_konto_2_abrufen.lock' + language: python3 stop_after_if: - expr: "false" + error_message: null + expr: 'false' skip_if_stopped: false - - id: send_to_talk summary: Jede E-Mail an Nextcloud Talk senden value: type: forloopflow - iterator: - type: javascript - expr: "[...results.fetch_emails_1, ...results.fetch_emails_2]" - skip_failures: false - parallel: false modules: - id: send_message summary: Nachricht an Nextcloud Talk senden value: type: rawscript - language: python3 - content: "!inline send_message.py" + content: '!inline nachricht_an_nextcloud_talk_senden.py' input_transforms: email: type: javascript @@ -75,3 +51,31 @@ value: nextcloud_config: type: javascript expr: flow_input.nextcloud_config + lock: '!inline nachricht_an_nextcloud_talk_senden.lock' + language: python3 + iterator: + type: javascript + expr: '[...results.fetch_emails_1, ...results.fetch_emails_2]' + parallel: false + skip_failures: false +schema: + $schema: http://json-schema.org/draft-07/schema + type: object + properties: + imap_config_1: + type: object + description: 'IMAP Konto 1 (Ressource: f/mail_to_talk/imap_config)' + format: resource-imap + imap_config_2: + type: object + description: 'IMAP Konto 2 (Ressource: f/mail_to_talk/imap_config_2)' + format: resource-imap + nextcloud_config: + type: object + description: 'Nextcloud Zugangsdaten (Ressource: + f/mail_to_talk/nextcloud_talk_config)' + format: resource-nextcloud + required: + - imap_config_1 + - imap_config_2 + - nextcloud_config diff --git a/f/mail_to_talk/mail_to_talk__flow/nachricht_an_nextcloud_talk_senden.lock b/f/mail_to_talk/mail_to_talk__flow/nachricht_an_nextcloud_talk_senden.lock new file mode 100644 index 0000000..3aacf1d --- /dev/null +++ b/f/mail_to_talk/mail_to_talk__flow/nachricht_an_nextcloud_talk_senden.lock @@ -0,0 +1 @@ +# py: 3.12 diff --git a/f/mail_to_talk/mail_to_talk__flow/send_message.py b/f/mail_to_talk/mail_to_talk__flow/nachricht_an_nextcloud_talk_senden.py similarity index 100% rename from f/mail_to_talk/mail_to_talk__flow/send_message.py rename to f/mail_to_talk/mail_to_talk__flow/nachricht_an_nextcloud_talk_senden.py diff --git a/f/mail_to_talk/mail_to_talk__flow/ungelesene_e-mails_konto_1_abrufen.lock b/f/mail_to_talk/mail_to_talk__flow/ungelesene_e-mails_konto_1_abrufen.lock new file mode 100644 index 0000000..3aacf1d --- /dev/null +++ b/f/mail_to_talk/mail_to_talk__flow/ungelesene_e-mails_konto_1_abrufen.lock @@ -0,0 +1 @@ +# py: 3.12 diff --git a/f/mail_to_talk/mail_to_talk__flow/fetch_emails.py b/f/mail_to_talk/mail_to_talk__flow/ungelesene_e-mails_konto_1_abrufen.py similarity index 100% rename from f/mail_to_talk/mail_to_talk__flow/fetch_emails.py rename to f/mail_to_talk/mail_to_talk__flow/ungelesene_e-mails_konto_1_abrufen.py diff --git a/f/mail_to_talk/mail_to_talk__flow/ungelesene_e-mails_konto_2_abrufen.lock b/f/mail_to_talk/mail_to_talk__flow/ungelesene_e-mails_konto_2_abrufen.lock new file mode 100644 index 0000000..3aacf1d --- /dev/null +++ b/f/mail_to_talk/mail_to_talk__flow/ungelesene_e-mails_konto_2_abrufen.lock @@ -0,0 +1 @@ +# py: 3.12 diff --git a/f/mail_to_talk/mail_to_talk__flow/ungelesene_e-mails_konto_2_abrufen.py b/f/mail_to_talk/mail_to_talk__flow/ungelesene_e-mails_konto_2_abrufen.py new file mode 100644 index 0000000..a38e5c7 --- /dev/null +++ b/f/mail_to_talk/mail_to_talk__flow/ungelesene_e-mails_konto_2_abrufen.py @@ -0,0 +1,85 @@ +import imaplib +import email +from email.header import decode_header +from typing import TypedDict, Optional, List +from datetime import datetime +import email.utils + + +class imap(TypedDict): + host: str + port: int + user: str + password: str + mailbox: Optional[str] + + +def decode_str(value) -> str: + if value is None: + return "" + parts = decode_header(value) + result = [] + for part, charset in parts: + if isinstance(part, bytes): + result.append(part.decode(charset or "utf-8", errors="replace")) + else: + result.append(part) + return "".join(result) + + +def main(imap_config: imap) -> List[dict]: + host = imap_config["host"] + port = imap_config["port"] + user = imap_config["user"] + password = imap_config["password"] + mailbox = imap_config.get("mailbox") or "INBOX" + + if port == 993: + client = imaplib.IMAP4_SSL(host, port) + else: + client = imaplib.IMAP4(host, port) + + client.login(user, password) + client.select(mailbox, readonly=False) + + # Nur ungelesene E-Mails suchen + status, data = client.search(None, "UNSEEN") + if status != "OK" or not data[0]: + client.logout() + return [] + + uids = data[0].split() + emails = [] + + for uid in uids: + status, msg_data = client.fetch(uid, "(BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)])") + if status != "OK": + continue + + raw = msg_data[0][1] + msg = email.message_from_bytes(raw) + + subject = decode_str(msg.get("Subject", "(Kein Betreff)")) + from_raw = msg.get("From", "Unbekannt") + from_addr = decode_str(from_raw) + + date_raw = msg.get("Date", "") + try: + parsed_date = email.utils.parsedate_to_datetime(date_raw) + date_str = parsed_date.strftime("%d.%m.%Y %H:%M:%S") + except Exception: + date_str = date_raw or "Unbekanntes Datum" + + # Als gelesen markieren + client.store(uid, "+FLAGS", "\\Seen") + + emails.append({ + "subject": subject, + "from": from_addr, + "date": date_str, + "uid": int(uid), + "account": user, + }) + + client.logout() + return emails diff --git a/wmill-lock.yaml b/wmill-lock.yaml index 811f644..3f8dee7 100644 --- a/wmill-lock.yaml +++ b/wmill-lock.yaml @@ -1,24 +1,38 @@ version: v2 locks: - f/Backup/backup_restore_orchestrator__flow+__flow_hash: 6579d1cc5b707758f5c529c9c0b453405a99cfa90aa14dc5f9fdd4495005be10 + f/Backup/backup_restore_orchestrator__flow+__flow_hash: 04867048ce98eccbc211d3c2f7309b1a1d17b32cdae32db0ca75edfb11eeb64e f/Backup/backup_restore_orchestrator__flow+aktive_datastores_aus_db_holen.my.sql: 7565dc4d025614f12cc4e54ca92a74d331d920dbd7b6ebee29d4ce3d5a5fe4dc f/Backup/backup_restore_orchestrator__flow+alle_freien_restore-server_holen.py: 2e1b0c1cabe1a48ec69f1633c75aa381bf0484a0f06ec7720652bc4dd0a1c8bb f/Backup/backup_restore_orchestrator__flow+alte_restore-ordner_auf_backup-server_loeschen.py: cc49da8a46103d3efb447cd6171212e95047311ebd350217034b8e87b65b8d09 - f/Backup/backup_restore_orchestrator__flow+ersten_restore_pro_server_starten.py: b809e5fafa560de53fcbd73fa18801f33920460e83d7affe572809e1cbc45974 + f/Backup/backup_restore_orchestrator__flow+ersten_restore_pro_server_starten.py: a971ed9dcf02683b291532cf4077a14335472ef50bef9d7debe61a1838ff4e3a + f/Backup/backup_restore_orchestrator__flow+flow-fehler_per_nextcloud_talk_melden.py: 990b913603c30753e21e9e501cc6395c6eef207f92a198b86b02436e20fd2795 f/Backup/backup_restore_orchestrator__flow+job_initialisieren_&_backup-queue_aus_pbs_aufbauen.py: 31bbfac68741bf617a8ce3fe501e3cb3d74512db8bb7e72055f29e9e0af70be2 - f/Backup/backup_restore_orchestrator__flow+script_deployen_&_pbs-datastores_auf_allen_servern_registrieren.py: 72cd9c759e2716cb13a0df0f4867af6bbbde1dcd353f25cb2f3dd48d703cd7f1 - f/Backup/backup_restore_orchestrator__flow+ssh-credentials_fuer_alle_restore-server_aus_bitwarden.py: 231782a10f9e9379f3b11ca2df1db30eecbcc211618ec4cc58bf10b36fd7d870 - f/Backup/backup_restore_orchestrator__flow+webhook_verarbeiten_&_naechsten_restore_auf_demselben_server_starten.py: 086c41014a56f4aa24abe0233793614005646227e4564e7c310a2ca17331fe28 + f/Backup/backup_restore_orchestrator__flow+script_deployen_&_pbs-datastores_auf_allen_servern_registrieren.py: b94953d1ba007149e2fb185ba4aad3405e569e66614a59003a91a2044d17f224 + f/Backup/backup_restore_orchestrator__flow+ssh-credentials_fuer_alle_restore-server_aus_bitwarden_(fallback).py: be5de491d2857f1e2bf5c93d35f59e1e56d6755b02650e163349ae30c89114cf + f/Backup/backup_restore_orchestrator__flow+ssh-key_aus_db_testen.py: 14a06d11eee684888f0b80b16b1b73a4487c46ec620abe843fb874deced92a02 + f/Backup/backup_restore_orchestrator__flow+webhook_verarbeiten_&_naechsten_restore_auf_demselben_server_starten.py: 33075530a84d944744a40476214c7b10549c069320a1af26186c406afb4f20e1 f/Backup/backup_restore_report___nextcloud_talk__flow+__flow_hash: f3c1a6a92392d6a7217b07f74799698b6be048e4706f2114f905cdbcf336a176 f/Backup/backup_restore_report___nextcloud_talk__flow+letzten_job_aus_db_holen_&_report_zusammenbauen.py: 10fce98d17bacb4c3314e50b9bbd321ce4a407ed27304f52f108088601f12ff5 f/Backup/backup_restore_report___nextcloud_talk__flow+nachricht_an_nextcloud_talk_senden.py: eb69eafe6fc5e3012774e1c8ce37b00540c6662e9c7302f915f6562d3dae0c0a + f/mail_to_talk/mail_to_talk__flow+__flow_hash: f280641696d7fbf1ec71f348c9d45725814c0b7297aeaf893eea045bcfe23fbb + f/mail_to_talk/mail_to_talk__flow+nachricht_an_nextcloud_talk_senden.py: abcc3d48bff32ac2103190fad1c4deb7b40eb3b312ac51bd940559e42e31b78a + f/mail_to_talk/mail_to_talk__flow+ungelesene_e-mails_konto_1_abrufen.py: c9ea5af563459e188485d97b47c37719c9f7fa194454b4c3ebee380d180eada7 + f/mail_to_talk/mail_to_talk__flow+ungelesene_e-mails_konto_2_abrufen.py: c9ea5af563459e188485d97b47c37719c9f7fa194454b4c3ebee380d180eada7 f/Proxmox/proxmox_backup_webhook__flow+__flow_hash: 47428b1bccf563b4b976bf2c36410fc018e429711b5d0c2cf77829c26ffea5fa f/Proxmox/proxmox_backup_webhook__flow+in_mysql_speichern.py: 6d2538c237800143595937eb64dcfdc1483e84a888e8c00dbd8820ae4aff95dc f/Proxmox/proxmox_backup_webhook__flow+nachricht_an_nextcloud_talk.py: f2993562a0f827bc8720a49b3246d34f6a0135c0f28845e5495eed5514d2476c f/Proxmox/proxmox_backup_webhook__flow+payload_parsen_&_aufbereiten.py: 563813669ff3fdef34311459bed488fbfd3725ce645ad70b483b4731c05f4e78 + f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen: 54ed4946e9da375bbc8876731879af7acc10ecb4bedd381776ee07b681f49689 + f/Reporting/exchange_logins__flow/ex-server_mit_rport: 1993acb6da2d88cc99cd30e9b98088f21df113d3041de6827897ce527661f39b + f/Reporting/exchange_logins__flow/powershell_via_rport: 2bb1e3959830fa8880ed5ebe053253de4da5df4a5aeac4b510fce722d39c9817 f/Reporting/rdp_logins__flow+__flow_hash: 08fa7852bd83a57bb43363c7398c43dea022b8a5cdc639cc34456281459a6034 f/Reporting/rdp_logins__flow+einzelnen_login-eintrag_einfügen.ts: 37bbfc68c31c7ebf6046b87c66a05996ef5ae38ad24dcb4de152879d969ab6f9 f/Reporting/rdp_logins__flow+powershell_via_rport.io_ausführen.ts: fec88de017a5f9566e9cdbd7b4b49ff9c96447629ef211b51d047026aa051726 f/Reporting/rdp_logins__flow+rds-server_mit_rport.io_clients_abgleichen.ts: dea12a10507f9dc7357d4ba06b306b921381bbab9a5ed7f95682e6e49787c224 + f/Reporting/run_sql_events__flow+__flow_hash: ad83045810a5f0c184ddbf066afb3fa80d693473ef3f0f05bb29f6fb27f0522f + f/Reporting/run_sql_events__flow+erstellt_betreff_und_body_mit_allen_ergebnissen_der_calls.ts: 532dbb2974448436d6540b670a9bb2683ee953dc3b7038d12ee9cfb09c956442 + f/Reporting/run_sql_events__flow+extrahiert_alle_call-statements_aus_dem_event_definition-body.ts: e57324d04d0e43fb603ff5ac404acd7c78f517035214b806c396609768a20ea4 + f/Reporting/run_sql_events__flow+extrahiert_event_definition-string_aus_den_zeilen.ts: 1041df7e4d2c5b17fb2470ee6b865a470e94f20a0aaabb74336f657f0e37d8ab + f/Reporting/run_sql_events__flow+führt_eine_einzelne_call_aus,_sendet_bei_fehler_pushover_und_liefert_ergebnis.ts: 06d9872fe89a38571603e6fcfd6597625f15dda3cc688a448be92a7adba78f23 f/Server/Bitwarden_Data_Export__flow+__flow_hash: 72c83e1fd73a374808b1be09a46a286ecc4b4eee806141a2570fc1703942cbbc f/Server/Bitwarden_Data_Export__flow+c.py: 90f7b8c407a4632f882bc1051c6b3827d97df336d787d2d1c3b4bcd5f0063db5