From 885ec7e678b5984e219cf7f0941cdb40ba607bed Mon Sep 17 00:00:00 2001 From: Eliezer Croitoru Date: Tue, 11 Aug 2026 19:04:35 +0300 Subject: [PATCH] 1 --- Dockerfile.bridge | 12 ++++++ app.py | 67 ++++++++++++++++++++++++++++++++++ docker-compose-with-uptime.yml | 42 +++++++++++++++++++++ docker-compose.yml | 15 ++++++++ mapping.json | 6 +++ mikrotik-script-examples.rsc | 19 ++++++++++ testing.sh | 10 +++++ 7 files changed, 171 insertions(+) create mode 100644 Dockerfile.bridge create mode 100644 app.py create mode 100644 docker-compose-with-uptime.yml create mode 100644 docker-compose.yml create mode 100644 mapping.json create mode 100644 mikrotik-script-examples.rsc create mode 100644 testing.sh diff --git a/Dockerfile.bridge b/Dockerfile.bridge new file mode 100644 index 0000000..8b82fe1 --- /dev/null +++ b/Dockerfile.bridge @@ -0,0 +1,12 @@ +FROM python:3.11-slim + +WORKDIR /app + +RUN pip install --no-cache-dir flask gunicorn + +COPY app.py . +COPY mapping.json . + +EXPOSE 3000 + +CMD ["gunicorn", "--bind", "0.0.0.0:3000", "app:app"] diff --git a/app.py b/app.py new file mode 100644 index 0000000..97ae690 --- /dev/null +++ b/app.py @@ -0,0 +1,67 @@ +import json +import os +import urllib.parse +import urllib.request +from flask import Flask, request, jsonify + +app = Flask(__name__) + +KUMA_BASE_URL = os.getenv("KUMA_BASE_URL", "http://localhost:3001/api/push") +MAPPING_FILE = os.getenv("MAPPING_FILE", "mapping.json") +# Shared secret for authentication +WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET", "super-secret-key-change-me") + +def load_mapping(): + if not os.path.exists(MAPPING_FILE): + return {} + try: + with open(MAPPING_FILE, "r") as f: + return json.load(f) + except Exception as e: + print(f"[ERROR] Failed to parse {MAPPING_FILE}: {e}") + return {} + +@app.route("/webhook/ppp", methods=["POST"]) +def ppp_webhook(): + # 1. Validate Secret Token in Header + auth_header = request.headers.get("X-API-Key") + if not auth_header or auth_header != WEBHOOK_SECRET: + print(f"[WARN] Unauthorized webhook attempt from IP: {request.remote_addr}") + return jsonify({"status": "error", "message": "Unauthorized"}), 401 + + data = request.get_json(silent=True) or {} + + user = data.get("user") + status = data.get("status") + ip = data.get("ip", "0.0.0.0") + msg = data.get("msg", "") + ping = data.get("ping", 0) + + if not user or not status: + return jsonify({"status": "error", "message": "Missing required fields"}), 400 + + mapping = load_mapping() + token = mapping.get(user) + + if not token: + print(f"[WARN] No token found for user: '{user}'") + return jsonify({"status": "ignored", "message": f"User '{user}' not mapped."}), 404 + + kuma_status = "up" if str(status).lower() == "up" else "down" + if not msg: + msg = f"PPP status is {kuma_status.upper()} for user '{user}' ({ip})" + + encoded_msg = urllib.parse.quote(msg) + kuma_url = f"{KUMA_BASE_URL}/{token}?status={kuma_status}&msg={encoded_msg}&ping={ping}" + + try: + req = urllib.request.Request(kuma_url, headers={"User-Agent": "MikroTik-Bridge/1.0"}) + with urllib.request.urlopen(req, timeout=5) as resp: + print(f"[INFO] Forwarded push for '{user}' -> status: {kuma_status}") + return jsonify({"status": "success"}), 200 + except Exception as e: + print(f"[ERROR] Failed to contact Uptime Kuma: {e}") + return jsonify({"status": "error", "message": str(e)}), 500 + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=3000) diff --git a/docker-compose-with-uptime.yml b/docker-compose-with-uptime.yml new file mode 100644 index 0000000..4f9504f --- /dev/null +++ b/docker-compose-with-uptime.yml @@ -0,0 +1,42 @@ +version: '3.8' + +services: + # Uptime Kuma Monitoring Service + uptime-kuma: + image: louislam/uptime-kuma:1 + container_name: uptime-kuma + restart: always + ports: + - "3001:3001" + volumes: + - kuma-data:/app/data + networks: + - kuma-net + + # MikroTik to Uptime Kuma Python Webhook Bridge + mikrotik-bridge: + build: + context: ./bridge + dockerfile: Dockerfile + container_name: mikrotik-kuma-bridge + restart: always + ports: + - "3000:3000" + environment: + # Internal Docker network URL pointing to the Uptime Kuma service container + - KUMA_BASE_URL=http://uptime-kuma:3001/api/push + - MAPPING_FILE=mapping.json + volumes: + # Mount mapping.json so you can edit tokens on the host without rebuilding the container + - ./bridge/mapping.json:/app/mapping.json:ro + depends_on: + - uptime-kuma + networks: + - kuma-net + +networks: + kuma-net: + driver: bridge + +volumes: + kuma-data: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..594100f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +services: + mikrotik-bridge: + build: + context: ./ + dockerfile: Dockerfile.bridge + container_name: mikrotik-kuma-bridge + restart: always + ports: + - "3000:3000" + environment: + - KUMA_BASE_URL=http://uptime-kuma:3001/api/push + - MAPPING_FILE=mapping.json + - WEBHOOK_SECRET=MySuperSecretToken123! + volumes: + - ./bridge/mapping.json:/app/mapping.json:ro diff --git a/mapping.json b/mapping.json new file mode 100644 index 0000000..8dfc711 --- /dev/null +++ b/mapping.json @@ -0,0 +1,6 @@ +{ + "john_doe": "A1b2C3d4E5", + "branch_office_vpn": "F6g7H8i9J0", + "site2site_tunnel": "K1l2M3n4O5", + "admin_laptop": "P6q7R8s9T0" +} diff --git a/mikrotik-script-examples.rsc b/mikrotik-script-examples.rsc new file mode 100644 index 0000000..b69a407 --- /dev/null +++ b/mikrotik-script-examples.rsc @@ -0,0 +1,19 @@ +# --- Define Local Variables --- +:local status "up" +:local serverUrl "http://localhost/webhook-test.php" +:local apiKey "MySuperSecretToken123!" + +# --- Fetch PPP System Variables safely --- +:local u $"user" +:local lip $"local-address" +:local rip $"remote-address" +:local cid $"caller-id" +:local iface $"interface" + +:local msg ("PPP session " . $status . " on interface " . $iface) + +# --- Construct JSON Body --- +:local jsonBody "{\"user\":\"$u\", \"local_ip\":\"$lip\", \"ip\":\"$rip\", \"caller_id\":\"$cid\", \"interface\":\"$iface\", \"status\":\"$status\", \"msg\":\"$msg\"}" + +# --- Send Webhook --- +/tool fetch url=$serverUrl http-method=post http-header-field="Content-Type: application/json,X-API-Key: $apiKey" http-data=$jsonBody keep-result=no diff --git a/testing.sh b/testing.sh new file mode 100644 index 0000000..b5c438a --- /dev/null +++ b/testing.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +curl -X POST http://localhost:3000/webhook/ppp \ + -H "Content-Type: application/json" \ + -H "X-API-Key: MySuperSecretToken123!" \ + -d '{ + "user": "john_doe", + "ip": "10.0.0.5", + "status": "up" + }'