From a5d75ffd04e097821b5e48f4831780e1196fa2b3 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Wed, 20 May 2026 20:54:27 -0500 Subject: [PATCH] chore: remove monitoring infrastructure (moved to mokogitea-private) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker compose, Grafana dashboards, library panels, and monitoring scripts are infrastructure config — they belong in the private repo, not the public moko-platform. Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) --- monitoring/docker-compose.yml | 186 --------- .../grafana/library-panels/cpu-usage.json | 89 ---- .../grafana/library-panels/disk-usage.json | 45 -- .../library-panels/docker-container-cpu.json | 32 -- .../docker-container-memory.json | 32 -- .../grafana/library-panels/memory-usage.json | 48 --- .../library-panels/mysql-connections.json | 38 -- .../mysql-queries-per-second.json | 69 --- .../library-panels/network-traffic.json | 79 ---- .../library-panels/nginx-connections.json | 43 -- .../library-panels/nginx-request-rate.json | 46 -- .../grafana/library-panels/response-time.json | 45 -- .../library-panels/service-health.json | 64 --- .../ssl-certificate-expiry.json | 68 --- .../library-panels/uptime-availability.json | 60 --- monitoring/grafana/mokowaas-dashboard.json | 394 ------------------ monitoring/joomla-version-audit.php | 330 --------------- monitoring/ssl-check.php | 312 -------------- monitoring/uptime-probe.php | 295 ------------- 19 files changed, 2275 deletions(-) delete mode 100644 monitoring/docker-compose.yml delete mode 100644 monitoring/grafana/library-panels/cpu-usage.json delete mode 100644 monitoring/grafana/library-panels/disk-usage.json delete mode 100644 monitoring/grafana/library-panels/docker-container-cpu.json delete mode 100644 monitoring/grafana/library-panels/docker-container-memory.json delete mode 100644 monitoring/grafana/library-panels/memory-usage.json delete mode 100644 monitoring/grafana/library-panels/mysql-connections.json delete mode 100644 monitoring/grafana/library-panels/mysql-queries-per-second.json delete mode 100644 monitoring/grafana/library-panels/network-traffic.json delete mode 100644 monitoring/grafana/library-panels/nginx-connections.json delete mode 100644 monitoring/grafana/library-panels/nginx-request-rate.json delete mode 100644 monitoring/grafana/library-panels/response-time.json delete mode 100644 monitoring/grafana/library-panels/service-health.json delete mode 100644 monitoring/grafana/library-panels/ssl-certificate-expiry.json delete mode 100644 monitoring/grafana/library-panels/uptime-availability.json delete mode 100644 monitoring/grafana/mokowaas-dashboard.json delete mode 100644 monitoring/joomla-version-audit.php delete mode 100644 monitoring/ssl-check.php delete mode 100644 monitoring/uptime-probe.php diff --git a/monitoring/docker-compose.yml b/monitoring/docker-compose.yml deleted file mode 100644 index acfea36..0000000 --- a/monitoring/docker-compose.yml +++ /dev/null @@ -1,186 +0,0 @@ -networks: - monitoring: - driver: bridge -volumes: - prometheus_data: null - grafana_data: null -services: - prometheus: - image: prom/prometheus:latest - container_name: prometheus - restart: unless-stopped - ports: - - 127.0.0.1:9091:9090 - volumes: - - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro - - ./targets:/etc/prometheus/targets:ro - - prometheus_data:/prometheus - command: - - --config.file=/etc/prometheus/prometheus.yml - - --storage.tsdb.path=/prometheus - - --storage.tsdb.retention.time=90d - - --web.enable-lifecycle - extra_hosts: - - host.docker.internal:host-gateway - networks: - - monitoring - healthcheck: - test: - - CMD - - wget - - -qO- - - http://localhost:9090/-/healthy - interval: 30s - timeout: 5s - retries: 3 - node-exporter: - image: prom/node-exporter:latest - container_name: node-exporter - restart: unless-stopped - ports: - - 127.0.0.1:9100:9100 - volumes: - - /proc:/host/proc:ro - - /sys:/host/sys:ro - - /:/rootfs:ro - - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket:ro - - /var/lib/prometheus/node-exporter:/textfile:ro - command: - - --path.procfs=/host/proc - - --path.sysfs=/host/sys - - --path.rootfs=/rootfs - - --collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/) - - --collector.netclass.ignored-devices=^(veth.*|br-.*|docker.*)$$ - - --collector.diskstats.device-exclude=^(ram|loop|fd|dm-)\d+$$ - - --collector.systemd - - --collector.systemd.unit-include=.+ - - --collector.textfile.directory=/textfile - pid: host - security_opt: - - apparmor:unconfined - networks: - - monitoring - healthcheck: - test: - - CMD - - wget - - --spider - - -q - - http://localhost:9100/metrics - interval: 30s - timeout: 5s - retries: 3 - cadvisor: - image: gcr.io/cadvisor/cadvisor:latest - container_name: cadvisor - restart: unless-stopped - ports: - - 127.0.0.1:8082:8080 - volumes: - - /:/rootfs:ro - - /var/run:/var/run:ro - - /sys:/sys:ro - - /var/lib/docker/:/var/lib/docker:ro - - /dev/disk/:/dev/disk:ro - privileged: true - devices: - - /dev/kmsg - networks: - - monitoring - healthcheck: - test: - - CMD - - wget - - --spider - - -q - - http://localhost:8080/healthz - interval: 30s - timeout: 5s - retries: 3 - nginx-exporter: - image: nginx/nginx-prometheus-exporter:latest - container_name: nginx-exporter - restart: unless-stopped - network_mode: host - command: - - --nginx.scrape-uri=http://127.0.0.1:8888/nginx_status - - --web.listen-address=0.0.0.0:9113 - grafana: - image: grafana/grafana:latest - container_name: grafana - restart: unless-stopped - ports: - - 127.0.0.1:3001:3000 - environment: - - GF_SECURITY_ADMIN_USER=jmiller - - GF_SECURITY_ADMIN_PASSWORD=#2918HeatherfieldDrive - - GF_SERVER_ROOT_URL=https://bench.mokoconsulting.tech/ - - GF_SERVER_DOMAIN=bench.mokoconsulting.tech - - GF_AUTH_ANONYMOUS_ENABLED=true - - GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer - - GF_AUTH_ANONYMOUS_ORG_ID=1 - - GF_USERS_ALLOW_SIGN_UP=false - - GF_USERS_ALLOW_ORG_CREATE=false - - GF_SECURITY_COOKIE_SECURE=true - - GF_SECURITY_STRICT_TRANSPORT_SECURITY=true - - GF_SECURITY_X_CONTENT_TYPE_OPTIONS=true - - GF_SECURITY_X_XSS_PROTECTION=true - - GF_LOG_MODE=console - - GF_LOG_LEVEL=debug - - GF_USERS_DEFAULT_THEME=dark - - GF_BRANDING_APP_TITLE=Moko Bench - - GF_BRANDING_LOGIN_TITLE=Moko Consulting - - GF_BRANDING_LOGIN_SUBTITLE=Server Performance Dashboard - - GF_DATE_FORMATS_FULL_DATE=YYYY-MM-DD HH:mm:ss - - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-polystat-panel,yesoreyeram-infinity-datasource,natel-discrete-panel - - GF_AUTH_GOOGLE_ENABLED=true - - GF_AUTH_GOOGLE_CLIENT_ID=349391103517-oiq974b2gq4r3t9f9cf43im31gtruhml.apps.googleusercontent.com - - GF_AUTH_GOOGLE_CLIENT_SECRET=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 - - GF_AUTH_GOOGLE_SCOPES=openid email profile - - GF_AUTH_GOOGLE_AUTH_URL=https://accounts.google.com/o/oauth2/v2/auth - - GF_AUTH_GOOGLE_TOKEN_URL=https://oauth2.googleapis.com/token - - GF_AUTH_GOOGLE_ALLOWED_DOMAINS=mokoconsulting.tech - - GF_AUTH_GOOGLE_ALLOW_SIGN_UP=true - - GF_AUTH_GOOGLE_AUTO_LOGIN=false - - GF_AUTH_GOOGLE_SKIP_ORG_ROLE_SYNC=true - - GF_USERS_AUTO_ASSIGN_ORG_ROLE=Admin - volumes: - - grafana_data:/var/lib/grafana - - ./grafana/provisioning:/etc/grafana/provisioning:ro - - ./grafana/custom.ini:/etc/grafana/grafana.ini:ro - - ./grafana/dashboards:/var/lib/grafana/dashboards:ro - networks: - - monitoring - depends_on: - prometheus: - condition: service_healthy - healthcheck: - test: - - CMD - - wget - - --spider - - -q - - http://localhost:3000/api/health - interval: 30s - timeout: 5s - retries: 3 - mysqld-exporter: - image: prom/mysqld-exporter:latest - container_name: mysqld-exporter - restart: unless-stopped - network_mode: host - volumes: - - /opt/gitea-server-setup/docker/monitoring/.mysqld-exporter.cnf:/cfg/.my.cnf:ro - environment: - MYSQLD_EXPORTER_PASSWORD: exporter_moko_2026 - command: - - --config.my-cnf=/cfg/.my.cnf - - --web.listen-address=127.0.0.1:9104 - healthcheck: - test: - - CMD-SHELL - - wget -q --spider http://localhost:9104/metrics || exit 1 - interval: 30s - timeout: 10s - retries: 3 - start_period: 15s diff --git a/monitoring/grafana/library-panels/cpu-usage.json b/monitoring/grafana/library-panels/cpu-usage.json deleted file mode 100644 index 16d8d6d..0000000 --- a/monitoring/grafana/library-panels/cpu-usage.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "name": "CPU Usage", - "kind": 1, - "model": { - "description": "", - "fieldConfig": { - "defaults": { - "custom": { - "fillOpacity": 20, - "gradientMode": "scheme", - "lineWidth": 2 - }, - "max": 1, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 0.7 - }, - { - "color": "red", - "value": 0.9 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Yesterday CPU" - }, - "properties": [ - { - "id": "custom.lineStyle", - "value": { - "dash": [ - 10, - 10 - ], - "fill": "dash" - } - }, - { - "id": "custom.fillOpacity", - "value": 0 - }, - { - "id": "custom.lineWidth", - "value": 1 - }, - { - "id": "color", - "value": { - "fixedColor": "rgba(255,255,255,0.35)", - "mode": "fixed" - } - } - ] - } - ] - }, - "targets": [ - { - "expr": "1 - avg(rate(node_cpu_seconds_total{mode=\"idle\"}[5m]))", - "legendFormat": "Total CPU", - "refId": "A" - }, - { - "expr": "1 - avg(rate(node_cpu_seconds_total{mode=\"idle\"}[5m] offset 1d))", - "legendFormat": "Yesterday CPU", - "refId": "B" - } - ], - "options": { - "legend": { "displayMode": "list", "placement": "right", "calcs": [] }, - "tooltip": { "mode": "multi" } - }, - "title": "CPU Usage %", - "type": "timeseries" - } -} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/disk-usage.json b/monitoring/grafana/library-panels/disk-usage.json deleted file mode 100644 index 4170794..0000000 --- a/monitoring/grafana/library-panels/disk-usage.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "Disk Usage", - "kind": 1, - "model": { - "description": "", - "fieldConfig": { - "defaults": { - "max": 1, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 0.75 - }, - { - "color": "red", - "value": 0.9 - } - ] - }, - "unit": "percentunit" - } - }, - "targets": [ - { - "expr": "1 - (node_filesystem_avail_bytes{mountpoint=\"/\",fstype!=\"tmpfs\"} / node_filesystem_size_bytes{mountpoint=\"/\",fstype!=\"tmpfs\"})", - "legendFormat": "/ (root)", - "refId": "A" - }, - { - "expr": "1 - (node_filesystem_avail_bytes{mountpoint=\"/mnt/backup\",fstype!=\"tmpfs\"} / node_filesystem_size_bytes{mountpoint=\"/mnt/backup\",fstype!=\"tmpfs\"})", - "legendFormat": "/mnt/backup", - "refId": "B" - } - ], - "title": "Filesystem Usage", - "type": "gauge" - } -} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/docker-container-cpu.json b/monitoring/grafana/library-panels/docker-container-cpu.json deleted file mode 100644 index a6d4210..0000000 --- a/monitoring/grafana/library-panels/docker-container-cpu.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "Docker Container CPU", - "kind": 1, - "model": { - "description": "", - "fieldConfig": { - "defaults": { - "custom": { - "fillOpacity": 20, - "lineWidth": 2, - "stacking": { - "mode": "normal" - } - }, - "unit": "percentunit" - } - }, - "targets": [ - { - "expr": "rate(container_cpu_usage_seconds_total{name!=\"\",name!~\".*POD.*\"}[5m])", - "legendFormat": "{{name}}", - "refId": "A" - } - ], - "options": { - "legend": { "displayMode": "list", "placement": "right", "calcs": [] }, - "tooltip": { "mode": "multi" } - }, - "title": "CPU Usage by Container", - "type": "timeseries" - } -} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/docker-container-memory.json b/monitoring/grafana/library-panels/docker-container-memory.json deleted file mode 100644 index a35b2dd..0000000 --- a/monitoring/grafana/library-panels/docker-container-memory.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "Docker Container Memory", - "kind": 1, - "model": { - "description": "", - "fieldConfig": { - "defaults": { - "custom": { - "fillOpacity": 20, - "lineWidth": 2, - "stacking": { - "mode": "normal" - } - }, - "unit": "bytes" - } - }, - "targets": [ - { - "expr": "container_memory_usage_bytes{name!=\"\",name!~\".*POD.*\"}", - "legendFormat": "{{name}}", - "refId": "A" - } - ], - "options": { - "legend": { "displayMode": "list", "placement": "right", "calcs": [] }, - "tooltip": { "mode": "multi" } - }, - "title": "Memory Usage by Container", - "type": "timeseries" - } -} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/memory-usage.json b/monitoring/grafana/library-panels/memory-usage.json deleted file mode 100644 index e2c48b6..0000000 --- a/monitoring/grafana/library-panels/memory-usage.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "Memory Usage", - "kind": 1, - "model": { - "description": "", - "fieldConfig": { - "defaults": { - "custom": { - "fillOpacity": 30, - "lineWidth": 2, - "stacking": { - "group": "A", - "mode": "normal" - } - }, - "unit": "bytes" - } - }, - "targets": [ - { - "expr": "node_memory_MemTotal_bytes - node_memory_MemFree_bytes - node_memory_Buffers_bytes - node_memory_Cached_bytes - node_memory_SReclaimable_bytes", - "legendFormat": "Used", - "refId": "A" - }, - { - "expr": "node_memory_Buffers_bytes", - "legendFormat": "Buffers", - "refId": "B" - }, - { - "expr": "node_memory_Cached_bytes + node_memory_SReclaimable_bytes", - "legendFormat": "Cached", - "refId": "C" - }, - { - "expr": "node_memory_MemFree_bytes", - "legendFormat": "Free", - "refId": "D" - } - ], - "options": { - "legend": { "displayMode": "list", "placement": "right", "calcs": [] }, - "tooltip": { "mode": "multi" } - }, - "title": "Memory Usage", - "type": "timeseries" - } -} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/mysql-connections.json b/monitoring/grafana/library-panels/mysql-connections.json deleted file mode 100644 index d655f9d..0000000 --- a/monitoring/grafana/library-panels/mysql-connections.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "MySQL Connections", - "kind": 1, - "model": { - "description": "", - "fieldConfig": { - "defaults": { - "custom": { - "fillOpacity": 15, - "lineWidth": 2 - } - } - }, - "targets": [ - { - "expr": "mysql_global_status_threads_connected", - "legendFormat": "Connected", - "refId": "A" - }, - { - "expr": "mysql_global_status_threads_running", - "legendFormat": "Running", - "refId": "B" - }, - { - "expr": "mysql_global_variables_max_connections", - "legendFormat": "Max", - "refId": "C" - } - ], - "options": { - "legend": { "displayMode": "list", "placement": "right", "calcs": [] }, - "tooltip": { "mode": "multi" } - }, - "title": "Connections", - "type": "timeseries" - } -} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/mysql-queries-per-second.json b/monitoring/grafana/library-panels/mysql-queries-per-second.json deleted file mode 100644 index 0051daa..0000000 --- a/monitoring/grafana/library-panels/mysql-queries-per-second.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "MySQL Queries per Second", - "kind": 1, - "model": { - "description": "", - "fieldConfig": { - "defaults": { - "custom": { - "fillOpacity": 20, - "lineWidth": 2 - }, - "unit": "ops" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Yesterday" - }, - "properties": [ - { - "id": "custom.lineStyle", - "value": { - "dash": [ - 10, - 10 - ], - "fill": "dash" - } - }, - { - "id": "custom.fillOpacity", - "value": 0 - }, - { - "id": "custom.lineWidth", - "value": 1 - }, - { - "id": "color", - "value": { - "fixedColor": "rgba(255,255,255,0.35)", - "mode": "fixed" - } - } - ] - } - ] - }, - "targets": [ - { - "expr": "rate(mysql_global_status_queries[5m])", - "legendFormat": "Queries/s", - "refId": "A" - }, - { - "expr": "rate(mysql_global_status_queries[5m] offset 1d)", - "legendFormat": "Yesterday", - "refId": "B" - } - ], - "options": { - "legend": { "displayMode": "list", "placement": "right", "calcs": [] }, - "tooltip": { "mode": "multi" } - }, - "title": "Queries per Second", - "type": "timeseries" - } -} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/network-traffic.json b/monitoring/grafana/library-panels/network-traffic.json deleted file mode 100644 index 8250c70..0000000 --- a/monitoring/grafana/library-panels/network-traffic.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "name": "Network Traffic", - "kind": 1, - "model": { - "description": "", - "fieldConfig": { - "defaults": { - "custom": { - "fillOpacity": 20, - "lineWidth": 2 - }, - "unit": "bps" - }, - "overrides": [ - { - "matcher": { - "id": "byRegexp", - "options": "/Yesterday.*/" - }, - "properties": [ - { - "id": "custom.lineStyle", - "value": { - "dash": [ - 10, - 10 - ], - "fill": "dash" - } - }, - { - "id": "custom.fillOpacity", - "value": 0 - }, - { - "id": "custom.lineWidth", - "value": 1 - }, - { - "id": "color", - "value": { - "fixedColor": "rgba(255,255,255,0.35)", - "mode": "fixed" - } - } - ] - } - ] - }, - "targets": [ - { - "expr": "rate(node_network_receive_bytes_total{device!~\"lo|veth.*|br-.*|docker.*\"}[5m]) * 8", - "legendFormat": "RX {{device}}", - "refId": "A" - }, - { - "expr": "-rate(node_network_transmit_bytes_total{device!~\"lo|veth.*|br-.*|docker.*\"}[5m]) * 8", - "legendFormat": "TX {{device}}", - "refId": "B" - }, - { - "expr": "rate(node_network_receive_bytes_total{device!~\"lo|veth.*|br-.*|docker.*\"}[5m] offset 1d) * 8", - "legendFormat": "Yesterday RX {{device}}", - "refId": "C" - }, - { - "expr": "-rate(node_network_transmit_bytes_total{device!~\"lo|veth.*|br-.*|docker.*\"}[5m] offset 1d) * 8", - "legendFormat": "Yesterday TX {{device}}", - "refId": "D" - } - ], - "options": { - "legend": { "displayMode": "list", "placement": "right", "calcs": [] }, - "tooltip": { "mode": "multi" } - }, - "title": "Network Traffic", - "type": "timeseries" - } -} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/nginx-connections.json b/monitoring/grafana/library-panels/nginx-connections.json deleted file mode 100644 index 0f186fd..0000000 --- a/monitoring/grafana/library-panels/nginx-connections.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "Nginx Connections", - "kind": 1, - "model": { - "description": "", - "fieldConfig": { - "defaults": { - "custom": { - "fillOpacity": 20, - "lineWidth": 2 - } - } - }, - "targets": [ - { - "expr": "nginx_connections_active", - "legendFormat": "Active", - "refId": "A" - }, - { - "expr": "nginx_connections_reading", - "legendFormat": "Reading", - "refId": "B" - }, - { - "expr": "nginx_connections_writing", - "legendFormat": "Writing", - "refId": "C" - }, - { - "expr": "nginx_connections_waiting", - "legendFormat": "Waiting", - "refId": "D" - } - ], - "options": { - "legend": { "displayMode": "list", "placement": "right", "calcs": [] }, - "tooltip": { "mode": "multi" } - }, - "title": "Connections Over Time", - "type": "timeseries" - } -} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/nginx-request-rate.json b/monitoring/grafana/library-panels/nginx-request-rate.json deleted file mode 100644 index f4c8ccb..0000000 --- a/monitoring/grafana/library-panels/nginx-request-rate.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "Nginx Request Rate", - "kind": 1, - "model": { - "description": "", - "fieldConfig": { - "defaults": { - "custom": { - "fillOpacity": 25, - "gradientMode": "scheme", - "lineWidth": 2 - }, - "thresholds": { - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 50 - }, - { - "color": "red", - "value": 200 - } - ] - }, - "unit": "reqps" - } - }, - "targets": [ - { - "expr": "rate(nginx_http_requests_total[5m])", - "legendFormat": "Requests/s", - "refId": "A" - } - ], - "options": { - "legend": { "displayMode": "list", "placement": "right", "calcs": [] }, - "tooltip": { "mode": "multi" } - }, - "title": "Request Rate", - "type": "timeseries" - } -} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/response-time.json b/monitoring/grafana/library-panels/response-time.json deleted file mode 100644 index cc85792..0000000 --- a/monitoring/grafana/library-panels/response-time.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "Response Time", - "kind": 1, - "model": { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "description": "", - "fieldConfig": { - "defaults": { - "unit": "s" - } - }, - "options": { - "legend": { - "displayMode": "list", - "placement": "right" - }, - "tooltip": { - "mode": "multi" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "expr": "probe_http_duration_seconds{site_name=~\"$site\", job=\"blackbox-http\", phase=\"transfer\"}", - "legendFormat": "{{site_name}} transfer" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "expr": "probe_http_duration_seconds{site_name=~\"$site\", job=\"blackbox-http\", phase=\"processing\"}", - "legendFormat": "{{site_name}} processing" - } - ], - "title": "Response Time", - "type": "timeseries" - } -} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/service-health.json b/monitoring/grafana/library-panels/service-health.json deleted file mode 100644 index 94f25c6..0000000 --- a/monitoring/grafana/library-panels/service-health.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "Service Health", - "kind": 1, - "model": { - "description": "", - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Value" - }, - "properties": [ - { - "id": "mappings", - "value": [ - { - "options": { - "0": { - "color": "red", - "text": "DOWN" - }, - "1": { - "color": "green", - "text": "UP" - } - }, - "type": "value" - } - ] - } - ] - } - ] - }, - "targets": [ - { - "expr": "up", - "format": "table", - "instant": true, - "refId": "A" - } - ], - "title": "Scrape Target Health", - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": { - "Time": true, - "__name__": true - }, - "renameByName": { - "Value": "Status", - "instance": "Instance", - "job": "Service" - } - } - } - ], - "type": "table" - } -} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/ssl-certificate-expiry.json b/monitoring/grafana/library-panels/ssl-certificate-expiry.json deleted file mode 100644 index f3cd4f9..0000000 --- a/monitoring/grafana/library-panels/ssl-certificate-expiry.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "SSL Certificate Expiry", - "kind": 1, - "model": { - "description": "", - "fieldConfig": { - "defaults": { - "custom": { - "drawStyle": "line", - "fillOpacity": 10, - "lineWidth": 2, - "spanNulls": false, - "thresholdsStyle": { - "mode": "area" - } - }, - "decimals": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "yellow", - "value": 7 - }, - { - "color": "green", - "value": 30 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "min" - ], - "displayMode": "table", - "placement": "right" - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "expr": "moko_ssl_cert_expiry_seconds{domain=\"git.mokoconsulting.tech\"} / 86400", - "legendFormat": "git.mokoconsulting.tech \u2014 days", - "refId": "A" - }, - { - "expr": "moko_ssl_cert_expiry_seconds{domain=\"ntfy.mokoconsulting.tech\"} / 86400", - "legendFormat": "ntfy.mokoconsulting.tech \u2014 days", - "refId": "B" - } - ], - "title": "Certificate Expiry Timeline", - "type": "timeseries" - } -} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/uptime-availability.json b/monitoring/grafana/library-panels/uptime-availability.json deleted file mode 100644 index b59ebaa..0000000 --- a/monitoring/grafana/library-panels/uptime-availability.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "Uptime Availability", - "kind": 1, - "model": { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "description": "", - "fieldConfig": { - "defaults": { - "custom": { - "fillOpacity": 10, - "lineWidth": 2 - }, - "max": 1, - "min": 0, - "thresholds": { - "steps": [ - { - "color": "red", - "value": 0 - }, - { - "color": "yellow", - "value": 0.95 - }, - { - "color": "green", - "value": 0.99 - } - ] - }, - "unit": "percentunit" - } - }, - "options": { - "legend": { - "displayMode": "list", - "placement": "right", - "calcs": [] - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "expr": "avg_over_time(probe_success{site_name=~\"$site\", job=\"blackbox-http\"}[1h])", - "legendFormat": "{{site_name}}" - } - ], - "title": "Availability (30d)", - "type": "timeseries" - } -} \ No newline at end of file diff --git a/monitoring/grafana/mokowaas-dashboard.json b/monitoring/grafana/mokowaas-dashboard.json deleted file mode 100644 index 38d3378..0000000 --- a/monitoring/grafana/mokowaas-dashboard.json +++ /dev/null @@ -1,394 +0,0 @@ -{ - "description": "MokoWaaS endpoint monitoring — site health, Joomla core & extensions, backups, performance, and uptime", - "editable": true, - "panels": [ - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 100, - "title": "Site Status", - "type": "row" - }, - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { - "defaults": { - "noValue": "—", - "custom": { "align": "center", "cellOptions": { "type": "auto" } } - }, - "overrides": [ - { - "matcher": { "id": "byName", "options": "Site" }, - "properties": [ - { "id": "custom.width", "value": 300 }, - { "id": "custom.align", "value": "left" }, - { "id": "links", "value": [{ "title": "Open Site", "url": "${__value.text}", "targetBlank": true }] } - ] - }, - { - "matcher": { "id": "byName", "options": "Online" }, - "properties": [ - { "id": "mappings", "value": [{ "options": { "0": { "color": "orange", "text": "OFFLINE" }, "1": { "color": "green", "text": "ONLINE" } }, "type": "value" }] }, - { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "orange", "value": null }, { "color": "green", "value": 1 }] } }, - { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "basic" } } - ] - }, - { - "matcher": { "id": "byName", "options": "Status" }, - "properties": [ - { "id": "mappings", "value": [{ "options": { "0": { "color": "red", "text": "DOWN" }, "1": { "color": "green", "text": "UP" } }, "type": "value" }] }, - { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "green", "value": 1 }] } }, - { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "basic" } } - ] - }, - { - "matcher": { "id": "byName", "options": "HTTP" }, - "properties": [ - { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "green", "value": 200 }, { "color": "yellow", "value": 300 }, { "color": "orange", "value": 400 }, { "color": "red", "value": 500 }] } }, - { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "basic" } }, - { "id": "links", "value": [{ "title": "HTTP Status Code Reference", "url": "https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#${__value.raw}", "targetBlank": true }] } - ] - }, - { - "matcher": { "id": "byName", "options": "API" }, - "properties": [ - { "id": "mappings", "value": [{ "options": { "0": { "color": "red", "text": "NO" }, "1": { "color": "green", "text": "OK" } }, "type": "value" }] }, - { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "green", "value": 1 }] } }, - { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "basic" } } - ] - }, - { - "matcher": { "id": "byName", "options": "SSL" }, - "properties": [ - { "id": "decimals", "value": 0 }, - { "id": "unit", "value": "d" }, - { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "yellow", "value": 14 }, { "color": "green", "value": 30 }] } }, - { "id": "custom.cellOptions", "value": { "type": "color-text" } } - ] - }, - { - "matcher": { "id": "byName", "options": "Last Scrape" }, - "properties": [ - { "id": "unit", "value": "dateTimeFromNow" } - ] - } - ] - }, - "gridPos": { "x": 0, "y": 1, "w": 24, "h": 8 }, - "id": 1, - "options": { "showHeader": true, "cellHeight": "sm", "footer": { "show": false } }, - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "probe_success{site_name=~\"$site\", job=\"blackbox-http\"}", - "instant": true, "format": "table", "refId": "STATUS" - }, - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "label_replace(probe_http_status_code{site_name=~\"$site\", job=\"blackbox-http\"}, \"site_url\", \"$1\", \"instance\", \"(.+)\")", - "instant": true, "format": "table", "refId": "HTTP" - }, - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "label_replace(joomla_site_online{site=~\"$site\"}, \"site_name\", \"$1\", \"site\", \"(.+)\")", - "instant": true, "format": "table", "refId": "ONLINE" - }, - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "label_replace(joomla_site_api_reachable{site=~\"$site\"}, \"site_name\", \"$1\", \"site\", \"(.+)\")", - "instant": true, "format": "table", "refId": "API" - }, - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "(probe_ssl_earliest_cert_expiry{site_name=~\"$site\"} - time()) / 86400", - "instant": true, "format": "table", "refId": "SSL" - }, - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "label_replace(joomla_monitor_last_scrape{site=~\"$site\"} * 1000, \"site_name\", \"$1\", \"site\", \"(.+)\")", - "instant": true, "format": "table", "refId": "LASTSCRAPE" - } - ], - "transformations": [ - { "id": "joinByField", "options": { "byField": "site_name", "mode": "outer" } }, - { - "id": "filterFieldsByName", - "options": { - "include": { "pattern": "^(site_url|Value #).*" } - } - }, - { - "id": "organize", - "options": { - "renameByName": { - "site_url": "Site", - "Value #ONLINE": "Online", - "Value #STATUS": "Status", - "Value #HTTP": "HTTP", - "Value #API": "API", - "Value #SSL": "SSL", - "Value #LASTSCRAPE": "Last Scrape" - }, - "indexByName": { - "site_url": 0, - "Value #ONLINE": 1, - "Value #STATUS": 2, - "Value #HTTP": 3, - "Value #API": 4, - "Value #SSL": 5, - "Value #LASTSCRAPE": 6 - } - } - }, - { "id": "sortBy", "options": { "sort": [{ "field": "Site", "desc": false }] } } - ], - "title": "Site Health", - "type": "table" - }, - { - "collapsed": true, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 25 - }, - "id": 101, - "title": "Joomla Core & Updates", - "type": "row", - "panels": [ - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { - "defaults": { - "noValue": "—", - "custom": { "align": "center", "cellOptions": { "type": "auto" } } - }, - "overrides": [ - { - "matcher": { "id": "byName", "options": "Site" }, - "properties": [ - { "id": "custom.width", "value": 300 }, - { "id": "custom.align", "value": "left" }, - { "id": "links", "value": [{ "title": "Open Admin", "url": "${__value.text}/administrator/", "targetBlank": true }] } - ] - }, - { - "matcher": { "id": "byName", "options": "System" }, - "properties": [ - { "id": "mappings", "value": [{ "options": { "0": { "color": "green", "text": "CURRENT" }, "1": { "color": "red", "text": "UPDATE" } }, "type": "value" }] }, - { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "red", "value": 1 }] } }, - { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "basic" } } - ] - }, - { - "matcher": { "id": "byName", "options": "Ext Updates" }, - "properties": [ - { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "red", "value": 1 }] } }, - { "id": "custom.cellOptions", "value": { "type": "color-text" } }, - { "id": "mappings", "value": [{ "options": { "0": { "text": "—" } }, "type": "value" }] } - ] - }, - { - "matcher": { "id": "byName", "options": "Total" }, - "properties": [ - { "id": "mappings", "value": [{ "options": { "0": { "text": "—" } }, "type": "value" }] } - ] - }, - { - "matcher": { "id": "byName", "options": "Enabled" }, - "properties": [ - { "id": "mappings", "value": [{ "options": { "0": { "text": "—" } }, "type": "value" }] }, - { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] } }, - { "id": "custom.cellOptions", "value": { "type": "color-text" } } - ] - }, - { - "matcher": { "id": "byName", "options": "Disabled" }, - "properties": [ - { "id": "mappings", "value": [{ "options": { "0": { "text": "—" } }, "type": "value" }] }, - { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 5 }, { "color": "orange", "value": 20 }] } }, - { "id": "custom.cellOptions", "value": { "type": "color-text" } } - ] - } - ] - }, - "gridPos": { "x": 0, "y": 26, "w": 24, "h": 8 }, - "id": 10, - "options": { "showHeader": true, "cellHeight": "sm", "footer": { "show": false } }, - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "label_replace(joomla_core_version_info{site=~\"$site\"}, \"site_url\", \"$1\", \"exported_instance\", \"(.+)\")", - "instant": true, "format": "table", "refId": "VERSION" - }, - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "max by (site) (joomla_core_update_available{site=~\"$site\"})", - "instant": true, "format": "table", "refId": "SYSTEM" - }, - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "joomla_extensions_update_count{site=~\"$site\"} or joomla_extensions_total{site=~\"$site\"} * 0", - "instant": true, "format": "table", "refId": "EXTUPDATES" - }, - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "joomla_extensions_total{site=~\"$site\"}", - "instant": true, "format": "table", "refId": "TOTAL" - }, - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "joomla_extensions_enabled{site=~\"$site\"}", - "instant": true, "format": "table", "refId": "ENABLED" - }, - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "joomla_extensions_disabled{site=~\"$site\"}", - "instant": true, "format": "table", "refId": "DISABLED" - } - ], - "transformations": [ - { "id": "joinByField", "options": { "byField": "site", "mode": "outer" } }, - { "id": "filterFieldsByName", "options": { "include": { "pattern": "^(site_url|version|Value #(SYSTEM|EXTUPDATES|TOTAL|ENABLED|DISABLED))$" } } }, - { - "id": "organize", - "options": { - "renameByName": { - "site_url": "Site", - "version": "Version", - "Value #SYSTEM": "System", - "Value #EXTUPDATES": "Ext Updates", - "Value #TOTAL": "Total", - "Value #ENABLED": "Enabled", - "Value #DISABLED": "Disabled" - } - } - }, - { "id": "sortBy", "options": { "sort": [{ "field": "Site", "desc": false }] } } - ], - "title": "Joomla Core & Extensions", - "type": "table" - } - ] - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 46 - }, - "id": 102, - "title": "Performance", - "type": "row" - }, - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { "defaults": { "unit": "s" } }, - "gridPos": { "h": 8, "w": 16, "x": 0, "y": 47 }, - "id": 20, - "options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "multi" } }, - "targets": [ - { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "probe_http_duration_seconds{site_name=~\"$site\", job=\"blackbox-http\", phase=\"transfer\"}", "legendFormat": "{{site_name}} transfer" }, - { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "probe_http_duration_seconds{site_name=~\"$site\", job=\"blackbox-http\", phase=\"processing\"}", "legendFormat": "{{site_name}} processing" } - ], - "title": "Response Time", - "type": "timeseries" - }, - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { "defaults": { "max": 10, "min": 0, "thresholds": { "steps": [{ "color": "green", "value": 0 }, { "color": "yellow", "value": 2 }, { "color": "red", "value": 5 }] }, "unit": "s" } }, - "gridPos": { "h": 8, "w": 8, "x": 16, "y": 47 }, - "id": 22, - "targets": [ - { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "probe_duration_seconds{site_name=~\"$site\", job=\"blackbox-http\"}", "legendFormat": "{{site_name}}" } - ], - "title": "Total Duration", - "type": "gauge" - }, - { - "collapsed": false, - "gridPos": { "h": 1, "w": 24, "x": 0, "y": 55 }, - "id": 104, - "title": "Backup Status", - "type": "row" - }, - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { - "defaults": { "noValue": "—", "custom": { "align": "center", "cellOptions": { "type": "auto" } } }, - "overrides": [ - { "matcher": { "id": "byName", "options": "Site" }, "properties": [{ "id": "custom.width", "value": 300 }, { "id": "custom.align", "value": "left" }, { "id": "links", "value": [{ "title": "Manage Backups", "url": "${__value.text}/administrator/index.php?option=com_akeebabackup&view=Manage", "targetBlank": true }] }] }, - { "matcher": { "id": "byName", "options": "Status" }, "properties": [{ "id": "mappings", "value": [{ "options": { "0": { "color": "red", "text": "FAILED" }, "1": { "color": "green", "text": "OK" } }, "type": "value" }] }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "green", "value": 1 }] } }, { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "basic" } }] }, - { "matcher": { "id": "byName", "options": "Age" }, "properties": [{ "id": "unit", "value": "s" }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 172800 }, { "color": "red", "value": 604800 }] } }, { "id": "custom.cellOptions", "value": { "type": "color-text" } }, { "id": "mappings", "value": [{ "options": { "-1": { "text": "—" } }, "type": "value" }] }] }, - { "matcher": { "id": "byName", "options": "Records" }, "properties": [{ "id": "mappings", "value": [{ "options": { "0": { "text": "—" } }, "type": "value" }] }] } - ] - }, - "gridPos": { "x": 0, "y": 56, "w": 24, "h": 8 }, - "id": 40, - "options": { "showHeader": true, "cellHeight": "sm", "footer": { "show": false } }, - "targets": [ - { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "label_replace(max by (site, exported_instance) (joomla_backup_status{site=~\"$site\"}), \"site_url\", \"$1\", \"exported_instance\", \"(.+)\")", "instant": true, "format": "table", "refId": "STATUS" }, - { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "joomla_backup_age_seconds{site=~\"$site\"}", "instant": true, "format": "table", "refId": "AGE" }, - { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "joomla_backup_records_total{site=~\"$site\"}", "instant": true, "format": "table", "refId": "RECORDS" } - ], - "transformations": [ - { "id": "joinByField", "options": { "byField": "site", "mode": "outer" } }, - { "id": "filterFieldsByName", "options": { "include": { "pattern": "^(site_url|Value #).*" } } }, - { "id": "organize", "options": { "renameByName": { "site_url": "Site", "Value #STATUS": "Status", "Value #AGE": "Age", "Value #RECORDS": "Records" } } }, - { "id": "sortBy", "options": { "sort": [{ "field": "Site", "desc": false }] } } - ], - "title": "Backup Status", - "type": "table" - }, - { - "collapsed": false, - "gridPos": { "h": 1, "w": 24, "x": 0, "y": 68 }, - "id": 103, - "title": "Uptime History", - "type": "row" - }, - { - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { "defaults": { "custom": { "fillOpacity": 10, "lineWidth": 2 }, "max": 1, "min": 0, "thresholds": { "steps": [{ "color": "red", "value": 0 }, { "color": "yellow", "value": 0.95 }, { "color": "green", "value": 0.99 }] }, "unit": "percentunit" } }, - "gridPos": { "h": 8, "w": 24, "x": 0, "y": 69 }, - "id": 30, - "options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } }, - "targets": [ - { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "avg_over_time(probe_success{site_name=~\"$site\", job=\"blackbox-http\"}[1h])", "legendFormat": "{{site_name}}" } - ], - "title": "Availability (30d)", - "type": "timeseries" - } - ], - "refresh": "5m", - "tags": ["mokowaas", "joomla", "endpoints", "monitoring"], - "templating": { - "list": [ - { - "current": { "text": "All", "value": "$__all" }, - "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "includeAll": true, - "label": "Site", - "multi": true, - "name": "site", - "query": "label_values(probe_success{job=\"blackbox-http\", site_type=~\"mokowaas.*\"}, site_name)", - "refresh": 2, - "sort": 1, - "type": "query" - } - ] - }, - "time": { "from": "now-24h", "to": "now" }, - "timezone": "browser", - "title": "MokoWaaS", - "uid": "mokowaas", - "version": 1 -} diff --git a/monitoring/joomla-version-audit.php b/monitoring/joomla-version-audit.php deleted file mode 100644 index 9561e66..0000000 --- a/monitoring/joomla-version-audit.php +++ /dev/null @@ -1,330 +0,0 @@ -#!/usr/bin/env php - - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: MokoStandards.Scripts.Monitoring - * INGROUP: MokoStandards - * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform - * PATH: /monitoring/joomla-version-audit.php - * VERSION: 01.00.00 - * BRIEF: Audit Joomla core and extension versions across sites via the Joomla API - */ - -declare(strict_types=1); - -final class JoomlaVersionAudit -{ - /** @var array */ - private array $args = []; - - /** @var array, error: string}> */ - private array $results = []; - - public function run(): int - { - $this->args = $this->parseArgs(); - - $sites = $this->resolveSites(); - - if (count($sites) === 0) { - $this->log('No sites provided. Use --sites .'); - return 1; - } - - $latestVersion = $this->args['latest'] ?? null; - - foreach ($sites as $site) { - $this->log("Auditing: {$site['url']}"); - $entry = $this->auditSite($site, $latestVersion); - $this->results[] = $entry; - } - - if (!empty($this->args['json'])) { - $this->outputJson(); - } else { - $this->outputTable(); - } - - $hasBehind = false; - foreach ($this->results as $r) { - if ($r['behind'] || $r['error'] !== '') { - $hasBehind = true; - break; - } - } - - return $hasBehind ? 1 : 0; - } - - /** - * @return array - */ - private function parseArgs(): array - { - $args = [ - 'sites' => null, - 'json' => false, - 'latest' => null, - ]; - - $argv = $_SERVER['argv'] ?? []; - $argc = count($argv); - - for ($i = 1; $i < $argc; $i++) { - switch ($argv[$i]) { - case '--sites': - $args['sites'] = $argv[++$i] ?? null; - break; - case '--json': - $args['json'] = true; - break; - case '--latest': - $args['latest'] = $argv[++$i] ?? null; - break; - } - } - - return $args; - } - - /** - * @return list - */ - private function resolveSites(): array - { - $file = $this->args['sites'] ?? null; - - if (empty($file)) { - return []; - } - - if (!is_file($file) || !is_readable($file)) { - $this->log("Cannot read sites file: {$file}"); - return []; - } - - $raw = file_get_contents($file); - if ($raw === false) { - $this->log("Failed to read sites file: {$file}"); - return []; - } - - $data = json_decode($raw, true); - if (!is_array($data)) { - $this->log("Sites file is not valid JSON: {$file}"); - return []; - } - - $sites = []; - foreach ($data as $item) { - if (is_array($item) && isset($item['url'], $item['token'])) { - $sites[] = [ - 'url' => rtrim((string) $item['url'], '/'), - 'token' => (string) $item['token'], - ]; - } - } - - return $sites; - } - - /** - * @param array{url: string, token: string} $site - * @return array{url: string, joomlaVersion: string, behind: bool, extensions: array, error: string} - */ - private function auditSite(array $site, ?string $latestVersion): array - { - $entry = [ - 'url' => $site['url'], - 'joomlaVersion' => '', - 'behind' => false, - 'extensions' => [], - 'error' => '', - ]; - - // Fetch Joomla version from config/application endpoint - $configData = $this->apiGet($site['url'] . '/api/index.php/v1/config/application', $site['token']); - - if ($configData === null) { - $entry['error'] = 'Failed to fetch site configuration'; - return $entry; - } - - // The Joomla API returns config attributes; extract version from the response - $joomlaVersion = $this->extractJoomlaVersion($configData); - $entry['joomlaVersion'] = $joomlaVersion; - - // Compare against latest known version - if ($latestVersion !== null && $joomlaVersion !== '') { - if (version_compare($joomlaVersion, $latestVersion, '<')) { - $entry['behind'] = true; - } - } - - // Fetch extensions list - $extData = $this->apiGet($site['url'] . '/api/index.php/v1/extensions', $site['token']); - - if ($extData !== null && isset($extData['data']) && is_array($extData['data'])) { - foreach ($extData['data'] as $ext) { - $attrs = $ext['attributes'] ?? $ext; - $name = $attrs['name'] ?? $attrs['element'] ?? 'unknown'; - $version = $attrs['version'] ?? '?'; - - // Filter to meaningful extensions (components, modules, plugins, templates) - $type = $attrs['type'] ?? ''; - if (in_array($type, ['component', 'module', 'plugin', 'template', 'package', 'library'], true)) { - $entry['extensions'][] = [ - 'name' => (string) $name, - 'version' => (string) $version, - ]; - } - } - } - - return $entry; - } - - /** - * @return array|null - */ - private function apiGet(string $url, string $token): ?array - { - $context = stream_context_create([ - 'http' => [ - 'method' => 'GET', - 'timeout' => 30, - 'ignore_errors' => true, - 'follow_location' => 1, - 'max_redirects' => 3, - 'header' => implode("\r\n", [ - "X-Joomla-Token: {$token}", - 'Accept: application/vnd.api+json', - 'Content-Type: application/json', - ]), - ], - 'ssl' => [ - 'verify_peer' => true, - 'verify_peer_name' => true, - ], - ]); - - $body = @file_get_contents($url, false, $context); - if ($body === false) { - $this->log(" API request failed: {$url}"); - return null; - } - - $data = json_decode($body, true); - if (!is_array($data)) { - $this->log(" Invalid JSON response from: {$url}"); - return null; - } - - return $data; - } - - /** - * @param array $configData - */ - private function extractJoomlaVersion(array $configData): string - { - // Joomla's Web Services API returns config data in a JSON:API structure. - // The version may appear in data.attributes or in meta. - if (isset($configData['meta']['cms-version'])) { - return (string) $configData['meta']['cms-version']; - } - - // Fallback: look through data attributes for a version-like field - if (isset($configData['data']) && is_array($configData['data'])) { - foreach ($configData['data'] as $item) { - $attrs = $item['attributes'] ?? []; - // Some Joomla API responses put the version in the attributes - if (isset($attrs['cms-version'])) { - return (string) $attrs['cms-version']; - } - } - } - - // Second fallback: look for X-Powered-By or similar in response - if (isset($configData['data']['attributes'])) { - $attrs = $configData['data']['attributes']; - foreach ($attrs as $key => $value) { - if (stripos($key, 'version') !== false && is_string($value)) { - return $value; - } - } - } - - return 'unknown'; - } - - private function outputTable(): void - { - $latestVersion = $this->args['latest'] ?? null; - - foreach ($this->results as $r) { - $this->log(''); - $this->log(str_repeat('=', 70)); - $this->log("Site: {$r['url']}"); - - if ($r['error'] !== '') { - $this->log(" Error: {$r['error']}"); - continue; - } - - $versionDisplay = $r['joomlaVersion']; - if ($r['behind'] && $latestVersion !== null) { - $versionDisplay .= " (BEHIND, latest: {$latestVersion})"; - } - $this->log(" Joomla Version: {$versionDisplay}"); - - if (count($r['extensions']) === 0) { - $this->log(' Extensions: none found'); - continue; - } - - // Extension table - $colName = 4; - $colVersion = 7; - - foreach ($r['extensions'] as $ext) { - $colName = max($colName, strlen($ext['name'])); - $colVersion = max($colVersion, strlen($ext['version'])); - } - - $colName = min($colName, 50); - $colVersion = min($colVersion, 20); - - $fmt = " %-{$colName}s | %-{$colVersion}s"; - - $this->log(''); - $this->log(sprintf($fmt, 'Name', 'Version')); - $this->log(' ' . str_repeat('-', $colName + $colVersion + 4)); - - foreach ($r['extensions'] as $ext) { - $nameDisplay = strlen($ext['name']) > 50 ? substr($ext['name'], 0, 47) . '...' : $ext['name']; - $this->log(sprintf($fmt, $nameDisplay, $ext['version'])); - } - } - - $this->log(''); - } - - private function outputJson(): void - { - echo json_encode($this->results, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; - } - - private function log(string $message): void - { - fwrite(STDERR, $message . "\n"); - } -} - -$audit = new JoomlaVersionAudit(); -exit($audit->run()); diff --git a/monitoring/ssl-check.php b/monitoring/ssl-check.php deleted file mode 100644 index ecc3e3f..0000000 --- a/monitoring/ssl-check.php +++ /dev/null @@ -1,312 +0,0 @@ -#!/usr/bin/env php - - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: MokoStandards.Scripts.Monitoring - * INGROUP: MokoStandards - * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform - * PATH: /monitoring/ssl-check.php - * VERSION: 01.00.00 - * BRIEF: Check SSL certificate expiry dates for a list of domains - */ - -declare(strict_types=1); - -final class SslCheck -{ - /** @var array */ - private array $args = []; - - /** @var array */ - private array $results = []; - - public function run(): int - { - $this->args = $this->parseArgs(); - - $domains = $this->resolveDomains(); - - if (count($domains) === 0) { - $this->log('No domains provided. Use --domains or --domain .'); - return 1; - } - - $warnDays = (int) ($this->args['warn-days'] ?? 30); - - foreach ($domains as $domain) { - $this->log("Checking: {$domain}"); - $entry = $this->checkCert($domain, $warnDays); - $this->results[] = $entry; - } - - $hasIssue = false; - foreach ($this->results as $r) { - if ($r['status'] !== 'OK') { - $hasIssue = true; - break; - } - } - - if (!empty($this->args['json'])) { - $this->outputJson(); - } else { - $this->outputTable(); - } - - if ($hasIssue && !empty($this->args['notify'])) { - $this->sendNotification(); - } - - return $hasIssue ? 1 : 0; - } - - /** - * @return array - */ - private function parseArgs(): array - { - $args = [ - 'domains' => null, - 'domain' => null, - 'warn-days' => 30, - 'notify' => null, - 'json' => false, - ]; - - $argv = $_SERVER['argv'] ?? []; - $argc = count($argv); - - for ($i = 1; $i < $argc; $i++) { - switch ($argv[$i]) { - case '--domains': - $args['domains'] = $argv[++$i] ?? null; - break; - case '--domain': - $args['domain'] = $argv[++$i] ?? null; - break; - case '--warn-days': - $args['warn-days'] = (int) ($argv[++$i] ?? 30); - break; - case '--notify': - $args['notify'] = $argv[++$i] ?? null; - break; - case '--json': - $args['json'] = true; - break; - } - } - - return $args; - } - - /** - * @return list - */ - private function resolveDomains(): array - { - $domains = []; - - if (!empty($this->args['domains'])) { - $file = $this->args['domains']; - if (!is_file($file) || !is_readable($file)) { - $this->log("Cannot read domains file: {$file}"); - return []; - } - $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - if ($lines !== false) { - foreach ($lines as $line) { - $line = trim($line); - if ($line !== '' && $line[0] !== '#') { - $domains[] = $line; - } - } - } - } - - if (!empty($this->args['domain'])) { - $domains[] = trim($this->args['domain']); - } - - return $domains; - } - - /** - * @return array{domain: string, issuer: string, expires: string, daysLeft: int, status: string, error: string} - */ - private function checkCert(string $domain, int $warnDays): array - { - $entry = [ - 'domain' => $domain, - 'issuer' => '', - 'expires' => '', - 'daysLeft' => 0, - 'status' => 'EXPIRED', - 'error' => '', - ]; - - $context = stream_context_create([ - 'ssl' => [ - 'capture_peer_cert' => true, - 'verify_peer' => true, - 'verify_peer_name' => true, - 'SNI_enabled' => true, - 'peer_name' => $domain, - ], - ]); - - $errno = 0; - $errstr = ''; - $client = @stream_socket_client( - "ssl://{$domain}:443", - $errno, - $errstr, - 15, - STREAM_CLIENT_CONNECT, - $context - ); - - if ($client === false) { - $entry['error'] = "Connection failed: {$errstr} ({$errno})"; - return $entry; - } - - $params = stream_context_get_params($client); - fclose($client); - - if (!isset($params['options']['ssl']['peer_certificate'])) { - $entry['error'] = 'No peer certificate captured'; - return $entry; - } - - $certResource = $params['options']['ssl']['peer_certificate']; - $certInfo = openssl_x509_parse($certResource); - - if ($certInfo === false) { - $entry['error'] = 'Failed to parse certificate'; - return $entry; - } - - // Issuer - $issuerParts = $certInfo['issuer'] ?? []; - $entry['issuer'] = $issuerParts['O'] ?? $issuerParts['CN'] ?? 'Unknown'; - - // Expiry - $validTo = $certInfo['validTo_time_t'] ?? 0; - $entry['expires'] = date('Y-m-d', $validTo); - - $now = time(); - $daysLeft = (int) floor(($validTo - $now) / 86400); - $entry['daysLeft'] = $daysLeft; - - if ($daysLeft < 0) { - $entry['status'] = 'EXPIRED'; - } elseif ($daysLeft <= $warnDays) { - $entry['status'] = 'WARN'; - } else { - $entry['status'] = 'OK'; - } - - return $entry; - } - - private function outputTable(): void - { - $colDomain = 6; - $colIssuer = 6; - $colExpires = 10; - $colDays = 9; - $colStatus = 7; - - foreach ($this->results as $r) { - $colDomain = max($colDomain, strlen($r['domain'])); - $colIssuer = max($colIssuer, strlen($r['issuer'])); - } - - $colDomain = min($colDomain, 40); - $colIssuer = min($colIssuer, 30); - - $fmt = "%-{$colDomain}s | %-{$colIssuer}s | %-{$colExpires}s | %-{$colDays}s | %-{$colStatus}s"; - - $this->log(''); - $this->log(sprintf($fmt, 'Domain', 'Issuer', 'Expires', 'Days Left', 'Status')); - $this->log(str_repeat('-', $colDomain + $colIssuer + $colExpires + $colDays + $colStatus + 13)); - - foreach ($this->results as $r) { - $domainDisplay = strlen($r['domain']) > 40 ? substr($r['domain'], 0, 37) . '...' : $r['domain']; - $issuerDisplay = strlen($r['issuer']) > 30 ? substr($r['issuer'], 0, 27) . '...' : $r['issuer']; - $daysStr = $r['error'] !== '' ? 'N/A' : (string) $r['daysLeft']; - $expiresStr = $r['expires'] ?: 'N/A'; - - $this->log(sprintf($fmt, $domainDisplay, $issuerDisplay, $expiresStr, $daysStr, $r['status'])); - - if ($r['error'] !== '') { - $this->log(" Error: {$r['error']}"); - } - } - - $this->log(''); - } - - private function outputJson(): void - { - echo json_encode($this->results, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; - } - - private function sendNotification(): void - { - $ntfyUrl = $this->args['notify']; - if (empty($ntfyUrl)) { - return; - } - - $issues = []; - foreach ($this->results as $r) { - if ($r['status'] !== 'OK') { - $issues[] = $r['domain'] . ' — ' . $r['status'] - . ($r['daysLeft'] > 0 ? " ({$r['daysLeft']} days left)" : '') - . ($r['error'] !== '' ? " [{$r['error']}]" : ''); - } - } - - $message = "SSL certificate issues:\n" . implode("\n", $issues); - - $ch = curl_init($ntfyUrl); - if ($ch === false) { - $this->log('Failed to initialise curl for notification.'); - return; - } - - curl_setopt_array($ch, [ - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => $message, - CURLOPT_RETURNTRANSFER => true, - CURLOPT_TIMEOUT => 10, - CURLOPT_HTTPHEADER => [ - 'Title: SSL Certificate Alert', - 'Priority: high', - ], - ]); - - $result = curl_exec($ch); - if ($result === false) { - $this->log('Notification failed: ' . curl_error($ch)); - } else { - $this->log('Notification sent.'); - } - - curl_close($ch); - } - - private function log(string $message): void - { - fwrite(STDERR, $message . "\n"); - } -} - -$check = new SslCheck(); -exit($check->run()); diff --git a/monitoring/uptime-probe.php b/monitoring/uptime-probe.php deleted file mode 100644 index df341db..0000000 --- a/monitoring/uptime-probe.php +++ /dev/null @@ -1,295 +0,0 @@ -#!/usr/bin/env php - - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: MokoStandards.Scripts.Monitoring - * INGROUP: MokoStandards - * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform - * PATH: /monitoring/uptime-probe.php - * VERSION: 01.00.00 - * BRIEF: Check uptime and response time for a list of URLs - */ - -declare(strict_types=1); - -final class UptimeProbe -{ - /** @var array */ - private array $args = []; - - /** @var array */ - private array $results = []; - - public function run(): int - { - $this->args = $this->parseArgs(); - - $urls = $this->resolveUrls(); - - if (count($urls) === 0) { - $this->log('No URLs provided. Use --urls or --url .'); - return 1; - } - - $timeout = (int) ($this->args['timeout'] ?? 15); - - foreach ($urls as $url) { - $this->log("Probing: {$url}"); - $entry = $this->probe($url, $timeout); - $this->results[] = $entry; - } - - $hasFailure = false; - foreach ($this->results as $r) { - if ($r['result'] === 'FAIL') { - $hasFailure = true; - break; - } - } - - if (!empty($this->args['json'])) { - $this->outputJson(); - } else { - $this->outputTable(); - } - - if ($hasFailure && !empty($this->args['notify'])) { - $this->sendNotification(); - } - - return $hasFailure ? 1 : 0; - } - - /** - * @return array - */ - private function parseArgs(): array - { - $args = [ - 'urls' => null, - 'url' => null, - 'timeout' => 15, - 'notify' => null, - 'json' => false, - ]; - - $argv = $_SERVER['argv'] ?? []; - $argc = count($argv); - - for ($i = 1; $i < $argc; $i++) { - switch ($argv[$i]) { - case '--urls': - $args['urls'] = $argv[++$i] ?? null; - break; - case '--url': - $args['url'] = $argv[++$i] ?? null; - break; - case '--timeout': - $args['timeout'] = (int) ($argv[++$i] ?? 15); - break; - case '--notify': - $args['notify'] = $argv[++$i] ?? null; - break; - case '--json': - $args['json'] = true; - break; - } - } - - return $args; - } - - /** - * @return list - */ - private function resolveUrls(): array - { - $urls = []; - - if (!empty($this->args['urls'])) { - $file = $this->args['urls']; - if (!is_file($file) || !is_readable($file)) { - $this->log("Cannot read URL file: {$file}"); - return []; - } - $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - if ($lines !== false) { - foreach ($lines as $line) { - $line = trim($line); - if ($line !== '' && $line[0] !== '#') { - $urls[] = $line; - } - } - } - } - - if (!empty($this->args['url'])) { - $urls[] = trim($this->args['url']); - } - - return $urls; - } - - /** - * @return array{url: string, status: int, time: float, result: string, error: string} - */ - private function probe(string $url, int $timeout): array - { - $entry = [ - 'url' => $url, - 'status' => 0, - 'time' => 0.0, - 'result' => 'FAIL', - 'error' => '', - ]; - - $context = stream_context_create([ - 'http' => [ - 'method' => 'GET', - 'timeout' => $timeout, - 'follow_location' => 1, - 'max_redirects' => 5, - 'ignore_errors' => true, - ], - 'ssl' => [ - 'verify_peer' => true, - 'verify_peer_name' => true, - ], - ]); - - $start = microtime(true); - $body = @file_get_contents($url, false, $context); - $elapsed = round(microtime(true) - $start, 3); - $entry['time'] = $elapsed; - - if ($body === false) { - $entry['error'] = 'Connection failed or timed out'; - return $entry; - } - - // Parse status code from $http_response_header - $statusCode = 0; - if (isset($http_response_header) && is_array($http_response_header)) { - foreach ($http_response_header as $header) { - if (preg_match('/^HTTP\/[\d.]+ (\d{3})/', $header, $m)) { - $statusCode = (int) $m[1]; - } - } - } - $entry['status'] = $statusCode; - - // Check for PHP fatal errors in the response body - $fatalPatterns = [ - 'Fatal error:', - 'Parse error:', - 'Uncaught Exception', - 'Uncaught Error', - 'Stack trace:', - ]; - - foreach ($fatalPatterns as $pattern) { - if (stripos($body, $pattern) !== false) { - $entry['error'] = "PHP fatal error detected in response body"; - return $entry; - } - } - - if ($statusCode >= 200 && $statusCode < 400) { - $entry['result'] = 'PASS'; - } else { - $entry['error'] = "HTTP {$statusCode}"; - } - - return $entry; - } - - private function outputTable(): void - { - $colUrl = 4; - $colStatus = 6; - $colTime = 6; - $colResult = 6; - - foreach ($this->results as $r) { - $colUrl = max($colUrl, strlen($r['url'])); - } - - $colUrl = min($colUrl, 60); - - $fmt = "%-{$colUrl}s | %-{$colStatus}s | %-{$colTime}s | %-{$colResult}s"; - - $this->log(''); - $this->log(sprintf($fmt, 'URL', 'Status', 'Time', 'Result')); - $this->log(str_repeat('-', $colUrl + $colStatus + $colTime + $colResult + 10)); - - foreach ($this->results as $r) { - $urlDisplay = strlen($r['url']) > 60 ? substr($r['url'], 0, 57) . '...' : $r['url']; - $timeStr = $r['time'] . 's'; - $statusStr = $r['status'] > 0 ? (string) $r['status'] : 'ERR'; - $this->log(sprintf($fmt, $urlDisplay, $statusStr, $timeStr, $r['result'])); - } - - $this->log(''); - } - - private function outputJson(): void - { - echo json_encode($this->results, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; - } - - private function sendNotification(): void - { - $ntfyUrl = $this->args['notify']; - if (empty($ntfyUrl)) { - return; - } - - $failures = []; - foreach ($this->results as $r) { - if ($r['result'] === 'FAIL') { - $failures[] = $r['url'] . ' (' . ($r['error'] ?: 'HTTP ' . $r['status']) . ')'; - } - } - - $message = "Uptime probe failures:\n" . implode("\n", $failures); - - $ch = curl_init($ntfyUrl); - if ($ch === false) { - $this->log('Failed to initialise curl for notification.'); - return; - } - - curl_setopt_array($ch, [ - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => $message, - CURLOPT_RETURNTRANSFER => true, - CURLOPT_TIMEOUT => 10, - CURLOPT_HTTPHEADER => [ - 'Title: Uptime Probe Alert', - 'Priority: high', - ], - ]); - - $result = curl_exec($ch); - if ($result === false) { - $this->log('Notification failed: ' . curl_error($ch)); - } else { - $this->log('Notification sent.'); - } - - curl_close($ch); - } - - private function log(string $message): void - { - fwrite(STDERR, $message . "\n"); - } -} - -$probe = new UptimeProbe(); -exit($probe->run()); -- 2.52.0