11 Commits

5 changed files with 220 additions and 6 deletions

View File

@@ -2,6 +2,25 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [0.5.0] - 2026-03-08
### Features
- [`d641571`](https://gitea.pufereq.pl/judas/judas_client/commit/d6415712d97c27a1716a836e0cde440b486d9132) **connector.py**: send initial telemetry after HELLO
- [`9360fe9`](https://gitea.pufereq.pl/judas/judas_client/commit/9360fe9f3c600cd7c610e8fd6e2d80f91a8c341f) **connector.py**: add `self.client` property
- [`5235cef`](https://gitea.pufereq.pl/judas/judas_client/commit/5235cef103a4b12f84fbc9def6bb68660041d8e1) **client.py**: add initial telemetry gathering
### Refactor
- [`07b9ba6`](https://gitea.pufereq.pl/judas/judas_client/commit/07b9ba6fdf7194a06520357327048a33b309ed5b) **client.py**: improve CPU model retrivement on Windows
### Build
- [`14e69fc`](https://gitea.pufereq.pl/judas/judas_client/commit/14e69fc40606961d3e3cabbda9119211c81fc972) **uv.lock**: update judas_protocol to 0.9.1
- [`869763d`](https://gitea.pufereq.pl/judas/judas_client/commit/869763dde35d18522d4e84bb9436b1d5064364e8) **uv.lock**: add depedency on psutil
- [`dd5fccf`](https://gitea.pufereq.pl/judas/judas_client/commit/dd5fccf8ff7ab214cec277ce6f8ccbc39acc85bb) **pyproject.toml**: add depedency on psutil
- [`a4a9051`](https://gitea.pufereq.pl/judas/judas_client/commit/a4a9051130a304b6e2d6ed038c62f812dde2c6a4) **uv.lock**: update judas_protocol to 0.9.0
## [0.4.4] - 2026-03-05 ## [0.4.4] - 2026-03-05
### Refactor ### Refactor

View File

@@ -4,13 +4,14 @@ build-backend = "uv_build"
[project] [project]
name = "judas_client" name = "judas_client"
version = "0.4.4" version = "0.5.0"
description = "A client for judas, a remote PC fleet management system." description = "A client for judas, a remote PC fleet management system."
readme = "README.md" readme = "README.md"
authors = [] authors = []
requires-python = ">=3.13" requires-python = ">=3.13"
dependencies = [ dependencies = [
"judas-protocol", "judas-protocol",
"psutil>=7.2.2",
] ]
license = { text = "GPL-3.0+" } license = { text = "GPL-3.0+" }

View File

@@ -5,8 +5,12 @@ from __future__ import annotations
import logging as lg import logging as lg
import os import os
import platform
import subprocess
import uuid import uuid
from typing import Any
import psutil
from judas_protocol import Message from judas_protocol import Message
from judas_client.connector import Connector from judas_client.connector import Connector
@@ -41,12 +45,17 @@ class Client:
self.logger.debug(f"ID: {self.id}") self.logger.debug(f"ID: {self.id}")
self.connector: Connector = Connector( self.connector: Connector = Connector(
client=self,
mac_address=self.id, mac_address=self.id,
host=self.server_host, host=self.server_host,
port=self.server_port, port=self.server_port,
on_message=self.handle_message, on_message=self.handle_message,
) )
self.initial_telemetry: dict[str, Any] = (
self._gather_initial_telemetry()
)
def _get_mac_address(self) -> str: def _get_mac_address(self) -> str:
"""Get the MAC address of the client. """Get the MAC address of the client.
@@ -61,6 +70,144 @@ class Client:
) )
return mac_address return mac_address
def _get_cpu_model(self) -> str:
"""Get the CPU model name.
Returns:
str: The CPU model name, or "Unknown" if it cannot be determined.
"""
system = platform.system()
if system == "Windows":
try:
import winreg
key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"HARDWARE\DESCRIPTION\System\CentralProcessor\0",
)
model, _ = winreg.QueryValueEx(key, "ProcessorNameString")
winreg.CloseKey(key)
return model.strip()
except Exception:
return platform.processor()
elif system == "Linux":
try:
with open("/proc/cpuinfo", "r") as f:
for line in f:
if "model name" in line:
return line.split(":")[1].strip()
except Exception:
return platform.processor()
elif system == "Darwin":
try:
return (
subprocess.check_output(
["sysctl", "-n", "machdep.cpu.brand_string"]
)
.decode()
.strip()
)
except Exception:
return platform.processor()
return platform.processor()
def _gather_initial_telemetry(self) -> dict[str, Any]:
"""Gather initial telemetry data."""
self.logger.debug("Gathering initial telemetry data...")
inventory: dict[str, Any] = {}
# platform info
inventory["platform"] = {
"system": platform.system(),
"node": platform.node(),
"release": platform.release(),
"version": platform.version(),
"architecture": platform.architecture()[0],
}
# CPU information
cpu_frequency = psutil.cpu_freq()
inventory["cpu"] = {
"model": self._get_cpu_model(),
"physical_cores": psutil.cpu_count(logical=False),
"threads": psutil.cpu_count(logical=True),
"max_frequency_mhz": round(cpu_frequency.max, 2)
if cpu_frequency
else "Unknown",
"min_frequency_mhz": round(cpu_frequency.min, 2)
if cpu_frequency
else "Unknown",
}
# Memory information
virtual_memory = psutil.virtual_memory()
swap_memory = psutil.swap_memory()
inventory["memory"] = {
"total_ram": round(virtual_memory.total / (1024**2), 2),
"total_swap": round(swap_memory.total / (1024**2), 2),
}
# Disk information
disk_partitions = psutil.disk_partitions()
inventory["disks"] = []
for partition in disk_partitions:
try:
usage = psutil.disk_usage(partition.mountpoint)
inventory["disks"].append(
{
"device": partition.device,
"mountpoint": partition.mountpoint,
"filesystem": partition.fstype,
"total_size_gb": round(usage.total / (1024**3), 2),
}
)
except (PermissionError, OSError) as e:
self.logger.warning(
f"Permission denied for disk partition: {partition.device}\n{e}"
)
continue
# Network information
network_interfaces = psutil.net_if_addrs()
inventory["network"] = []
for interface_name, addresses in network_interfaces.items():
interface_data = {
"interface": interface_name,
"ip_addresses": [],
"mac_address": None,
}
for address in addresses:
if address.family.name in ["AF_LINK", "AF_PACKET"]:
interface_data["mac_address"] = address.address
elif address.family.name == "AF_INET":
interface_data["ip_addresses"].append(address.address)
inventory["network"].append(interface_data)
# User information
inventory["users"] = []
for user in psutil.users():
inventory["users"].append(
{
"name": user.name,
"terminal": user.terminal,
"host": user.host,
"started": user.started,
}
)
# Python environment information
inventory["python"] = {
"version": platform.python_version(),
"implementation": platform.python_implementation(),
}
return inventory
def handle_message(self, message: Message) -> None: def handle_message(self, message: Message) -> None:
"""Handle incoming messages. """Handle incoming messages.

View File

@@ -5,16 +5,20 @@ import logging as lg
import selectors import selectors
import socket import socket
import time import time
from typing import Callable from typing import Callable, TYPE_CHECKING
from judas_protocol import Category, ControlAction, Message from judas_protocol import Category, ControlAction, Message
if TYPE_CHECKING:
from judas_client.client import Client
class Connector: class Connector:
"""Connector class for managing TCP connection and message exchange.""" """Connector class for managing TCP connection and message exchange."""
def __init__( def __init__(
self, self,
client: Client,
mac_address: str, mac_address: str,
host: str, host: str,
port: int, port: int,
@@ -34,6 +38,8 @@ class Connector:
) )
self.logger.debug("Initializing Connector...") self.logger.debug("Initializing Connector...")
self.client: Client = client
self.host: str = host self.host: str = host
self.port: int = port self.port: int = port
@@ -110,6 +116,14 @@ class Connector:
hello_message: Message = Message.Control.hello(self.mac_address) hello_message: Message = Message.Control.hello(self.mac_address)
self.send(hello_message) self.send(hello_message)
def send_initial_telemetry(self) -> None:
"""Send initial telemetry data to the server."""
self.logger.debug("[*] Sending initial telemetry...")
telemetry_message: Message = Message.Telemetry.initial(
self.client.initial_telemetry
)
self.send(telemetry_message)
def close(self) -> None: def close(self) -> None:
"""Close the connection and clean up resources.""" """Close the connection and clean up resources."""
self.logger.debug("[*] Closing connection...") self.logger.debug("[*] Closing connection...")
@@ -171,6 +185,7 @@ class Connector:
self.logger.debug("[*] Connected, sending HELLO...") self.logger.debug("[*] Connected, sending HELLO...")
self.send_hello() self.send_hello()
self.send_initial_telemetry()
def run(self) -> None: def run(self) -> None:
"""Run the main event loop.""" """Run the main event loop."""

40
uv.lock generated
View File

@@ -275,10 +275,11 @@ wheels = [
[[package]] [[package]]
name = "judas-client" name = "judas-client"
version = "0.4.4" version = "0.5.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "judas-protocol" }, { name = "judas-protocol" },
{ name = "psutil" },
] ]
[package.dev-dependencies] [package.dev-dependencies]
@@ -298,7 +299,10 @@ test = [
] ]
[package.metadata] [package.metadata]
requires-dist = [{ name = "judas-protocol", git = "https://gitea.pufereq.pl/judas/judas_protocol.git" }] requires-dist = [
{ name = "judas-protocol", git = "https://gitea.pufereq.pl/judas/judas_protocol.git" },
{ name = "psutil", specifier = ">=7.2.2" },
]
[package.metadata.requires-dev] [package.metadata.requires-dev]
bump = [ bump = [
@@ -318,8 +322,8 @@ test = [
[[package]] [[package]]
name = "judas-protocol" name = "judas-protocol"
version = "0.8.0" version = "0.9.1"
source = { git = "https://gitea.pufereq.pl/judas/judas_protocol.git#a805ccf38edffadc1b8c8b276e60758c86516cd3" } source = { git = "https://gitea.pufereq.pl/judas/judas_protocol.git#085c34f232f95313d66db48a7d17bc25c92a35ae" }
[[package]] [[package]]
name = "markdown-it-py" name = "markdown-it-py"
@@ -412,6 +416,34 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
] ]
[[package]]
name = "psutil"
version = "7.2.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" },
{ url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" },
{ url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" },
{ url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" },
{ url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" },
{ url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" },
{ url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" },
{ url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" },
{ url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" },
{ url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" },
{ url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" },
{ url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" },
{ url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" },
{ url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" },
{ url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" },
{ url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" },
{ url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" },
{ url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" },
{ url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" },
{ url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" },
]
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.12.5" version = "2.12.5"