Commit 9beb537fb19e0bb59cc1471ec7a399dd925f3540

Commits
[COMMIT BEGIN]
commit 9beb537fb19e0bb59cc1471ec7a399dd925f3540
Author: 0x4248 <[email protected]>
Date:   Tue Feb 3 11:41:19 2026 +0000

    PulseWatch: init

diff --git a/basic_install.sh b/basic_install.sh
new file mode 100644
index 0000000..d656ec0
--- /dev/null
+++ b/basic_install.sh
@@ -0,0 +1,2 @@
+git clone https://github.com/0x4248/nexus /opt/nexus
+
diff --git a/toolkits/ESCPOS/src/server.py b/toolkits/ESCPOS/src/server.py
index 0588690..e8585fa 100644
--- a/toolkits/ESCPOS/src/server.py
+++ b/toolkits/ESCPOS/src/server.py
@@ -151,4 +151,4 @@ def index():
 # --- main ------------------------------------------------------------------
 
 if __name__ == "__main__":
-    app.run(host="0.0.0.0", port=80, debug=False, use_reloader=False)
+    app.run(host="0.0.0.0", port=8001, debug=False, use_reloader=False)
diff --git a/usr/net/Pulse/CMakeLists.txt b/usr/net/PulseArchive/CMakeLists.txt
similarity index 100%
rename from usr/net/Pulse/CMakeLists.txt
rename to usr/net/PulseArchive/CMakeLists.txt
diff --git a/usr/net/Pulse/src/client.cpp b/usr/net/PulseArchive/src/client.cpp
similarity index 100%
rename from usr/net/Pulse/src/client.cpp
rename to usr/net/PulseArchive/src/client.cpp
diff --git a/usr/net/Pulse/src/global.h b/usr/net/PulseArchive/src/global.h
similarity index 100%
rename from usr/net/Pulse/src/global.h
rename to usr/net/PulseArchive/src/global.h
diff --git a/usr/net/Pulse/src/server.cpp b/usr/net/PulseArchive/src/server.cpp
similarity index 100%
rename from usr/net/Pulse/src/server.cpp
rename to usr/net/PulseArchive/src/server.cpp
diff --git a/usr/net/Pulse/src/time.cpp b/usr/net/PulseArchive/src/time.cpp
similarity index 100%
rename from usr/net/Pulse/src/time.cpp
rename to usr/net/PulseArchive/src/time.cpp
diff --git a/usr/net/Pulse/src/time.h b/usr/net/PulseArchive/src/time.h
similarity index 100%
rename from usr/net/Pulse/src/time.h
rename to usr/net/PulseArchive/src/time.h
diff --git a/usr/net/PulseWatch/client.py b/usr/net/PulseWatch/client.py
new file mode 100644
index 0000000..ec2a7d3
--- /dev/null
+++ b/usr/net/PulseWatch/client.py
@@ -0,0 +1,77 @@
+import time
+import requests
+import collector
+import sys
+import uuid
+import socket
+
+SERVER = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:9000"
+HOSTNAME = socket.gethostname()
+
+def send_stats():
+    requests.post(
+        f"{SERVER}/ingest",
+        json=collector.collect(),
+        timeout=2
+    )
+
+def ping():
+    ping_id = str(uuid.uuid4())
+    t0 = time.time_ns()
+
+    r = requests.get(
+        f"{SERVER}/ping",
+        params={"ts": t0, "id": ping_id},
+        timeout=2
+    )
+
+    t1 = time.time_ns()
+    r.raise_for_status()
+
+    rtt_ms = (t1 - t0) / 1_000_000
+
+    # agent-side receipt
+    requests.post(
+        f"{SERVER}/ingest",
+        json={
+            "hostname": HOSTNAME,
+            "event": "ping_received",
+            "ping_id": ping_id,
+        },
+        timeout=2
+    )
+
+    # controller-side RTT
+    requests.post(
+        f"{SERVER}/ingest",
+        json={
+            "hostname": HOSTNAME,
+            "event": "ping_rtt",
+            "ping_id": ping_id,
+            "rtt_ms": round(rtt_ms, 3),
+        },
+        timeout=2
+    )
+
+if __name__ == "__main__":
+    while True:
+        try:
+            send_stats()
+            ping_id, rtt = ping()
+
+            # store RTT on controller side
+            requests.post(
+                f"{SERVER}/ingest",
+                json={
+                    "hostname": HOSTNAME,
+                    "event": "ping_rtt",
+                    "ping_id": ping_id,
+                    "rtt_ms": round(rtt, 3),
+                },
+                timeout=2
+            )
+
+        except Exception:
+            pass
+
+        time.sleep(5)
diff --git a/usr/net/PulseWatch/collector.py b/usr/net/PulseWatch/collector.py
new file mode 100644
index 0000000..c3d4809
--- /dev/null
+++ b/usr/net/PulseWatch/collector.py
@@ -0,0 +1,67 @@
+import socket
+import time
+
+def _read_kv(path):
+    out = {}
+    with open(path) as f:
+        for line in f:
+            k, *v = line.split()
+            out[k.rstrip(":")] = int(v[0])
+    return out
+
+def cpu_usage():
+    with open("/proc/stat") as f:
+        cpu = list(map(int, f.readline().split()[1:]))
+
+    idle = cpu[3] + cpu[4]
+    total = sum(cpu)
+    return idle, total
+
+_prev_idle, _prev_total = cpu_usage()
+
+def cpu_percent():
+    global _prev_idle, _prev_total
+    idle, total = cpu_usage()
+
+    didle = idle - _prev_idle
+    dtotal = total - _prev_total
+
+    _prev_idle, _prev_total = idle, total
+    return round(100 * (1 - didle / dtotal), 2)
+
+def mem_info():
+    m = _read_kv("/proc/meminfo")
+    used = m["MemTotal"] - m["MemAvailable"]
+    return {
+        "total_kb": m["MemTotal"],
+        "used_kb": used,
+        "free_kb": m["MemAvailable"],
+    }
+
+def net_bytes():
+    rx = tx = 0
+    with open("/proc/net/dev") as f:
+        for line in f.readlines()[2:]:
+            data = line.split()
+            rx += int(data[1])
+            tx += int(data[9])
+    return rx, tx
+
+def uptime():
+    with open("/proc/uptime") as f:
+        return int(float(f.readline().split()[0]))
+
+def collect():
+    rx, tx = net_bytes()
+    return {
+        "hostname": socket.gethostname(),
+        "timestamp": int(time.time()),
+        "status": "OK",
+        "faults": 0,
+        "cpu_percent": cpu_percent(),
+        "loadavg": open("/proc/loadavg").read().strip(),
+        "memory": mem_info(),
+        "uptime_sec": uptime(),
+        "net_rx_bytes": rx,
+        "net_tx_bytes": tx,
+    }
diff --git a/usr/net/PulseWatch/install_client.sh b/usr/net/PulseWatch/install_client.sh
new file mode 100644
index 0000000..1f2e702
--- /dev/null
+++ b/usr/net/PulseWatch/install_client.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+set -eu
+
+INSTALL_DIR="/opt/nexus/usr/net/PulseWatch"
+SERVICE="/etc/systemd/system/pulsewatch-client.service"
+
+SERVER_URL="${1:-http://localhost:9000}"
+
+echo "[+] Installing PulseWatch client"
+echo "    Server: ${SERVER_URL}"
+
+# permissions on code
+chown -R root:root "$INSTALL_DIR"
+chmod -R 755 "$INSTALL_DIR"
+
+# systemd service
+cat > "$SERVICE" <<EOF
+[Unit]
+Description=PulseWatch Client
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+ExecStart=/usr/bin/python3 ${INSTALL_DIR}/client.py ${SERVER_URL}
+WorkingDirectory=${INSTALL_DIR}
+Restart=always
+RestartSec=5
+
+NoNewPrivileges=true
+PrivateTmp=true
+ProtectSystem=strict
+ProtectHome=true
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+systemctl daemon-reload
+systemctl enable --now pulsewatch-client
+
+echo "[+] PulseWatch client installed and running"
diff --git a/usr/net/PulseWatch/install_server.sh b/usr/net/PulseWatch/install_server.sh
new file mode 100644
index 0000000..70be152
--- /dev/null
+++ b/usr/net/PulseWatch/install_server.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+set -eu
+
+INSTALL_DIR="/opt/nexus/usr/net/PulseWatch"
+DATA_DIR="/var/lib/pulsewatch"
+SERVICE="/etc/systemd/system/pulsewatch-server.service"
+
+echo "[+] Installing PulseWatch server"
+
+# user
+if ! id pulsewatch >/dev/null 2>&1; then
+    useradd -r -s /usr/sbin/nologin pulsewatch
+fi
+
+# data dir
+mkdir -p "$DATA_DIR"
+chown pulsewatch:pulsewatch "$DATA_DIR"
+chmod 750 "$DATA_DIR"
+
+# permissions on code
+chown -R root:root "$INSTALL_DIR"
+chmod -R 755 "$INSTALL_DIR"
+
+# systemd service
+cat > "$SERVICE" <<EOF
+[Unit]
+Description=PulseWatch Server
+After=network.target
+
+[Service]
+ExecStart=/usr/bin/python3 ${INSTALL_DIR}/server.py
+WorkingDirectory=${INSTALL_DIR}
+User=pulsewatch
+Group=pulsewatch
+Restart=always
+RestartSec=3
+
+NoNewPrivileges=true
+PrivateTmp=true
+ProtectSystem=strict
+ProtectHome=true
+ReadWritePaths=${DATA_DIR}
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+systemctl daemon-reload
+systemctl enable --now pulsewatch-server
+
+echo "[+] PulseWatch server installed and running"
diff --git a/usr/net/PulseWatch/server.py b/usr/net/PulseWatch/server.py
new file mode 100644
index 0000000..9bc8ec6
--- /dev/null
+++ b/usr/net/PulseWatch/server.py
@@ -0,0 +1,97 @@
+import json
+import os
+import time
+import threading
+import uuid
+from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
+from urllib.parse import urlparse, parse_qs
+
+DATA_DIR = "./data"
+os.makedirs(DATA_DIR, exist_ok=True)
+
+lock = threading.Lock()
+
+def store_stats(host, payload):
+    path = os.path.join(DATA_DIR, f"{host}.json")
+    ts = str(int(time.time()))
+
+    with lock:
+        data = {}
+        if os.path.exists(path):
+            with open(path, "r") as f:
+                data = json.load(f)
+
+        data[ts] = payload
+
+        with open(path, "w") as f:
+            json.dump(data, f)
+
+def store_ping(host, payload):
+    path = os.path.join(DATA_DIR, f"{host}_pings.json")
+    ts = str(int(time.time()))
+
+    with lock:
+        data = {}
+        if os.path.exists(path):
+            with open(path, "r") as f:
+                data = json.load(f)
+
+        data[ts] = payload
+
+        with open(path, "w") as f:
+            json.dump(data, f)
+
+
+class Handler(BaseHTTPRequestHandler):
+    def do_POST(self):
+        if self.path != "/ingest":
+            self.send_error(404)
+            return
+
+        length = int(self.headers.get("Content-Length", 0))
+        body = self.rfile.read(length)
+
+        try:
+            payload = json.loads(body)
+        except json.JSONDecodeError:
+            self.send_error(400)
+            return
+
+        host = payload.get("hostname") or self.client_address[0]
+
+        if payload.get("event") in ("ping_received", "ping_rtt"):
+            store_ping(host, payload)
+        else:
+            store_stats(host, payload)
+
+        self.send_response(200)
+        self.end_headers()
+        self.wfile.write(b"OK")
+
+    def do_GET(self):
+        if not self.path.startswith("/ping"):
+            self.send_error(404)
+            return
+
+        qs = parse_qs(urlparse(self.path).query)
+        ts = qs.get("ts", [None])[0]
+        ping_id = qs.get("id", [None])[0]
+
+        if ts is None or ping_id is None:
+            self.send_error(400)
+            return
+
+        self.send_response(200)
+        self.send_header("Content-Type", "application/json")
+        self.end_headers()
+        self.wfile.write(json.dumps({
+            "ts": ts,
+            "ping_id": ping_id
+        }).encode())
+
+    def log_message(self, *_):
+        pass
+
+if __name__ == "__main__":
+    server = ThreadingHTTPServer(("0.0.0.0", 9000), Handler)
+    server.serve_forever()
[COMMIT END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.