From 6ad9b1a93fb7e564f5d92f28173a3e2e615891b0 Mon Sep 17 00:00:00 2001 From: feldjaeger Date: Thu, 2 Apr 2026 11:49:03 +0200 Subject: [PATCH] feat: add backup-status service (port 9999, push API for borgmatic) --- backup-status/Dockerfile | 5 ++++ backup-status/app.py | 64 ++++++++++++++++++++++++++++++++++++++++ compose.yaml | 10 +++++++ 3 files changed, 79 insertions(+) create mode 100644 backup-status/Dockerfile create mode 100644 backup-status/app.py diff --git a/backup-status/Dockerfile b/backup-status/Dockerfile new file mode 100644 index 0000000..28fc863 --- /dev/null +++ b/backup-status/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.12-slim +RUN pip install flask +WORKDIR /app +COPY app.py . +CMD ["python", "app.py"] diff --git a/backup-status/app.py b/backup-status/app.py new file mode 100644 index 0000000..c31b2e6 --- /dev/null +++ b/backup-status/app.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +""" +Backup Status API +Hosts push after borgmatic backup, n8n polls this endpoint. +GET / → JSON summary of all hosts +POST /push?host=&status=ok|error&msg= → update host status +""" +from flask import Flask, request, jsonify +import json, os, time + +app = Flask(__name__) +DATA_FILE = "/data/backup_status.json" + +def load(): + if os.path.exists(DATA_FILE): + with open(DATA_FILE) as f: + return json.load(f) + return {} + +def save(data): + os.makedirs(os.path.dirname(DATA_FILE), exist_ok=True) + with open(DATA_FILE, "w") as f: + json.dump(data, f, indent=2) + +@app.route("/") +def status(): + data = load() + now = time.time() + result = {} + for host, info in data.items(): + age_h = (now - info.get("ts", 0)) / 3600 + if age_h > 26: + s = "KEIN BACKUP" + elif info.get("status") == "error": + s = "FEHLER" + else: + s = "OK" + result[host] = { + "status": s, + "last_backup": info.get("time", "unbekannt"), + "msg": info.get("msg", ""), + "age_h": round(age_h, 1) + } + return jsonify(result) + +@app.route("/push", methods=["POST", "GET"]) +def push(): + host = request.args.get("host") or request.form.get("host") + status = request.args.get("status", "ok") + msg = request.args.get("msg", "") + if not host: + return jsonify({"error": "host parameter required"}), 400 + data = load() + data[host] = { + "status": status, + "msg": msg, + "ts": time.time(), + "time": time.strftime("%Y-%m-%d %H:%M") + } + save(data) + return jsonify({"ok": True, "host": host, "status": status}) + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=9999) diff --git a/compose.yaml b/compose.yaml index 25b18a5..e44e5e8 100644 --- a/compose.yaml +++ b/compose.yaml @@ -152,3 +152,13 @@ services: - '--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|host|etc)($|/)' restart: unless-stopped + backup-status: + build: ./backup-status + container_name: backup-status + restart: always + ports: + - "9999:9999" + volumes: + - /app-config/backup_status_data:/data + networks: + - monitoring_network