import os import subprocess import csv import io from flask import Flask, jsonify app = Flask(__name__) # Config NODE_ID = os.environ.get("NODE_ID", "unknown-node") PUBLIC_IP = os.environ.get("PUBLIC_IP", "unknown-ip") BIND_IP = os.environ.get("BIND_IP", "127.0.0.1") CHRONY_HOST = "127.0.0.1" def run_chronyc(command): try: result = subprocess.run( ["chronyc", "-h", CHRONY_HOST, "-c"] + command, capture_output=True, text=True, timeout=2, check=True ) return result.stdout.strip() except Exception as e: print(f"Error running 'chronyc {command}': {e}") return None def parse_tracking(): raw_csv = run_chronyc(["tracking"]) if not raw_csv: return {"Error": "Could not run tracking command"} try: reader = csv.reader(io.StringIO(raw_csv)) headers = [ "Reference ID", "Ref Source IP", "Stratum", "Ref time (UTC)", "System time", "Last offset", "RMS offset", "Frequency", "Residual freq", "Skew", "Root delay", "Root dispersion", "Update interval", "Leap status" ] values = next(reader) return dict(zip(headers, values)) except Exception as e: return {"Error": f"Failed to parse tracking CSV: {e}", "RawData": raw_csv} def parse_sources(): raw_csv = run_chronyc(["sources"]) if not raw_csv: return [] try: reader = csv.reader(io.StringIO(raw_csv)) # --- FIX: Add 10th header 'Std Dev' --- headers = [ "Mode", "State", "Name/IP address", "Stratum", "Poll", "Reach", "LastRx", "Last sample", "Last sample original", "Std Dev" # Was: Last sample error ] sources_list = [] for row in reader: if row: sources_list.append(dict(zip(headers, row))) return sources_list except Exception as e: print(f"Error parsing sources: {e}") return [] @app.route('/fragment.json') def get_fragment(): tracking_data = parse_tracking() sources_data = parse_sources() return jsonify({ "node_id": NODE_ID, "public_ip": PUBLIC_IP, "report_generated_time": subprocess.run(["date", "-u", "+%Y-%m-%dT%H:%M:%SZ"], capture_output=True, text=True).stdout.strip(), "tracking": tracking_data, "sources": sources_data }) if __name__ == '__main__': app.run(host=BIND_IP, port=9898)