Converted to PyPy package

This commit is contained in:
Christian Busch 2025-05-23 18:03:13 +02:00
parent 6e6f22f5d4
commit 4878866ada
13 changed files with 218 additions and 243 deletions

15
.gitignore vendored
View File

@ -21,7 +21,6 @@ wheels/
*.egg
# Virtual Environment
.venv/
venv/
env/
ENV/
@ -32,9 +31,15 @@ ENV/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Testing
.coverage
htmlcov/
.pytest_cache/
# Project specific
# Distribution
dist/
build/
*.egg-info/
# Local configuration
config.yaml

6
MANIFEST.in Normal file
View File

@ -0,0 +1,6 @@
include LICENSE
include README.md
include requirements.txt
include config.yaml.example
recursive-include src/system2mqtt *.py
recursive-include src/system2mqtt/collectors *.py

View File

@ -1,88 +0,0 @@
#!/usr/bin/env python3
import os
import sys
import yaml
import paho.mqtt.client as mqtt
import time
def load_config():
"""Load configuration from YAML file."""
try:
with open('config.yaml', 'r') as f:
return yaml.safe_load(f)
except Exception as e:
print(f"Error loading config: {e}")
sys.exit(1)
def on_connect(client, userdata, flags, rc):
"""Callback for when the client connects to the MQTT broker."""
if rc == 0:
print("Connected to MQTT broker")
else:
print(f"Failed to connect, return code {rc}")
sys.exit(1)
def on_message(client, userdata, message):
"""Callback for when a message is received."""
userdata.append(message.topic)
def get_topics(client, timeout=1):
"""Get all system2mqtt topics from the MQTT broker."""
topics = []
client.user_data_set(topics)
client.on_message = on_message
# Subscribe to all topics
client.subscribe('#')
client.loop_start()
# Wait for messages
time.sleep(timeout)
client.loop_stop()
client.unsubscribe('#')
# Filter for system2mqtt topics
return [topic for topic in topics if 'system2mqtt' in topic]
def cleanup_topics(config):
"""Clean up MQTT topics."""
# Create MQTT client
client = mqtt.Client()
client.username_pw_set(config['mqtt']['username'], config['mqtt']['password'])
client.on_connect = on_connect
# Connect to MQTT broker
try:
print(f"Connecting to MQTT broker at {config['mqtt']['host']}:{config['mqtt']['port']}...")
client.connect(config['mqtt']['host'], config['mqtt']['port'], 60)
except Exception as e:
print(f"Failed to connect to MQTT broker: {e}")
sys.exit(1)
# Get all system2mqtt topics
print("Finding system2mqtt topics...")
topics = get_topics(client)
if not topics:
print("No system2mqtt topics found")
return
# Delete each topic
for topic in topics:
print(f"Deleting topic: {topic}")
client.publish(topic, "", retain=True)
time.sleep(0.1) # Small delay between deletions
# Wait for messages to be published
time.sleep(1)
# Stop the MQTT client loop and disconnect
client.disconnect()
print("Cleanup completed")
if __name__ == "__main__":
config = load_config()
cleanup_topics(config)

20
pyproject.toml Normal file
View File

@ -0,0 +1,20 @@
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.black]
line-length = 100
target-version = ['py38']
include = '\.pyi?$'
[tool.isort]
profile = "black"
multi_line_output = 3
line_length = 100
[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true

View File

@ -1,3 +1,8 @@
paho-mqtt==1.6.1
psutil==5.9.5
PyYAML==6.0.1
paho-mqtt>=2.0.0
psutil>=5.9.0
pyyaml>=6.0
black>=23.0.0
isort>=5.12.0
mypy>=1.0.0
pytest>=7.0.0
pytest-cov>=4.0.0

73
run.sh
View File

@ -1,73 +0,0 @@
#!/bin/bash
# Virtual environment directory
VENV_DIR=".venv"
# Function to show usage
show_usage() {
echo "Usage: $0 [start|stop|cleanup]"
echo
echo "Commands:"
echo " start - Start the system2mqtt service"
echo " stop - Stop the system2mqtt service"
echo " cleanup - Clean up all system2mqtt MQTT topics"
echo
exit 1
}
# Function to setup virtual environment
setup_venv() {
# Create virtual environment if it doesn't exist
if [ ! -d "$VENV_DIR" ]; then
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
fi
# Activate virtual environment
source "$VENV_DIR/bin/activate"
# Install/update dependencies
echo "Installing/updating dependencies..."
pip install -r requirements.txt
}
# Function to start the service
start_service() {
echo "Starting system2mqtt..."
setup_venv
python3 main.py
}
# Function to stop the service
stop_service() {
echo "Stopping system2mqtt..."
pkill -f "python3 main.py"
}
# Function to cleanup MQTT topics
cleanup_topics() {
echo "Running MQTT cleanup..."
setup_venv
python3 cleanup_mqtt.py
}
# Check if a command was provided
if [ $# -eq 0 ]; then
show_usage
fi
# Process command
case "$1" in
start)
start_service
;;
stop)
stop_service
;;
cleanup)
cleanup_topics
;;
*)
show_usage
;;
esac

43
setup.py Executable file
View File

@ -0,0 +1,43 @@
from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setup(
name="system2mqtt",
version="0.1.0",
author="Christian Busch",
author_email="hello@chbus.ch",
description="A system for monitoring hosts by collecting metrics and sending them to Home Assistant via MQTT",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://git.debilux.org/chris/system2mqtt",
package_dir={"": "src"},
packages=find_packages(where="src"),
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: System Administrators",
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX :: Linux",
"Operating System :: POSIX :: BSD :: FreeBSD",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Topic :: System :: Monitoring",
"Topic :: System :: Systems Administration",
],
python_requires=">=3.8",
install_requires=[
"paho-mqtt>=2.0.0",
"psutil>=5.9.0",
"pyyaml>=6.0",
],
entry_points={
"console_scripts": [
"system2mqtt=system2mqtt.main:main",
],
},
include_package_data=True,
)

View File

@ -0,0 +1,20 @@
"""
Collectors package for system2mqtt.
Contains various collectors for different system metrics.
"""
from .base_collector import BaseCollector
from .cpu_collector import CPUCollector
from .memory_collector import MemoryCollector
from .disk_collector import DiskCollector
from .network_collector import NetworkCollector
from .zfs_collector import ZFSCollector
__all__ = [
'BaseCollector',
'CPUCollector',
'MemoryCollector',
'DiskCollector',
'NetworkCollector',
'ZFSCollector',
]

View File

@ -10,83 +10,120 @@ import importlib
import paho.mqtt.client as mqtt
from typing import Dict, Any, List
import yaml
from pathlib import Path
def get_config_path():
"""Get the path to the config file.
Returns:
Path: Path to the config file
"""
# First check if config file is specified via environment variable
if config_path := os.environ.get('SYSTEM2MQTT_CONFIG'):
return Path(config_path)
# Then check user's config directory
if sys.platform == 'win32':
config_dir = Path(os.environ['APPDATA']) / 'system2mqtt'
else:
config_dir = Path.home() / '.config' / 'system2mqtt'
config_file = config_dir / 'config.yaml'
# Create config directory if it doesn't exist
config_dir.mkdir(parents=True, exist_ok=True)
# If config file doesn't exist, copy example config
if not config_file.exists():
example_config = Path(__file__).parent.parent.parent / 'config.yaml.example'
if example_config.exists():
import shutil
shutil.copy(example_config, config_file)
print(f"Created config file at {config_file}")
print("Please edit the config file and restart the application.")
sys.exit(1)
else:
print("Error: Neither config.yaml nor config.yaml.example found!")
sys.exit(1)
return config_file
# Load configuration
with open('config.yaml', 'r') as f:
config = yaml.safe_load(f)
# MQTT Configuration
MQTT_HOST = config['mqtt']['host']
MQTT_PORT = config['mqtt']['port']
MQTT_USERNAME = config['mqtt']['username']
MQTT_PASSWORD = config['mqtt']['password']
MQTT_CLIENT_ID = config['mqtt']['client_id']
MQTT_DISCOVERY_PREFIX = config['mqtt']['discovery_prefix']
# Get hostname
HOSTNAME = socket.gethostname()
# Get OS information
OS_INFO = {
'system': platform.system(),
'release': platform.release(),
'version': platform.version(),
'machine': platform.machine(),
'processor': platform.processor()
}
def get_collectors() -> List[str]:
"""Get list of available collector scripts."""
collectors = []
for file in os.listdir('collectors'):
if file.endswith('.py') and not file.startswith('__'):
collectors.append(file[:-3])
return collectors
def load_collector(collector_name: str) -> Any:
"""Load a collector module."""
try:
return importlib.import_module(f'collectors.{collector_name}')
except ImportError as e:
print(f"Error loading collector {collector_name}: {e}")
return None
def get_device_info() -> Dict[str, Any]:
"""Get device information for Home Assistant."""
return {
"identifiers": [f"system2mqtt_{HOSTNAME}"],
"name": f"System Metrics - {HOSTNAME}",
"model": OS_INFO['system'],
"manufacturer": "system2mqtt",
"sw_version": OS_INFO['version'],
"configuration_url": f"http://{HOSTNAME}",
"hw_version": OS_INFO['machine']
}
def get_device_attributes() -> Dict[str, Any]:
"""Get additional device attributes."""
return {
"operating_system": OS_INFO['system'],
"os_release": OS_INFO['release'],
"os_version": OS_INFO['version'],
"architecture": OS_INFO['machine'],
"processor": OS_INFO['processor']
}
def on_connect(client: mqtt.Client, userdata: Any, flags: Dict[str, Any], rc: int) -> None:
"""Callback for when the client connects to the MQTT broker."""
print(f"Connected with result code {rc}")
# Subscribe to any topics if needed
# client.subscribe("$SYS/#")
def on_disconnect(client: mqtt.Client, userdata: Any, rc: int) -> None:
"""Callback for when the client disconnects from the MQTT broker."""
print(f"Disconnected with result code {rc}")
if rc != 0:
print("Unexpected disconnection. Attempting to reconnect...")
def main():
"""Main function."""
config_path = get_config_path()
with open(config_path, 'r') as f:
config = yaml.safe_load(f)
# MQTT Configuration
MQTT_HOST = config['mqtt']['host']
MQTT_PORT = config['mqtt']['port']
MQTT_USERNAME = config['mqtt']['username']
MQTT_PASSWORD = config['mqtt']['password']
MQTT_CLIENT_ID = config['mqtt']['client_id']
MQTT_DISCOVERY_PREFIX = config['mqtt']['discovery_prefix']
# Get hostname
HOSTNAME = socket.gethostname()
# Get OS information
OS_INFO = {
'system': platform.system(),
'release': platform.release(),
'version': platform.version(),
'machine': platform.machine(),
'processor': platform.processor()
}
def get_collectors() -> List[str]:
"""Get list of available collector scripts."""
collectors = []
for file in os.listdir('collectors'):
if file.endswith('.py') and not file.startswith('__'):
collectors.append(file[:-3])
return collectors
def load_collector(collector_name: str) -> Any:
"""Load a collector module."""
try:
return importlib.import_module(f'collectors.{collector_name}')
except ImportError as e:
print(f"Error loading collector {collector_name}: {e}")
return None
def get_device_info() -> Dict[str, Any]:
"""Get device information for Home Assistant."""
return {
"identifiers": [f"system2mqtt_{HOSTNAME}"],
"name": f"System Metrics - {HOSTNAME}",
"model": OS_INFO['system'],
"manufacturer": "system2mqtt",
"sw_version": OS_INFO['version'],
"configuration_url": f"http://{HOSTNAME}",
"hw_version": OS_INFO['machine']
}
def get_device_attributes() -> Dict[str, Any]:
"""Get additional device attributes."""
return {
"operating_system": OS_INFO['system'],
"os_release": OS_INFO['release'],
"os_version": OS_INFO['version'],
"architecture": OS_INFO['machine'],
"processor": OS_INFO['processor']
}
def on_connect(client: mqtt.Client, userdata: Any, flags: Dict[str, Any], rc: int) -> None:
"""Callback for when the client connects to the MQTT broker."""
print(f"Connected with result code {rc}")
# Subscribe to any topics if needed
# client.subscribe("$SYS/#")
def on_disconnect(client: mqtt.Client, userdata: Any, rc: int) -> None:
"""Callback for when the client disconnects from the MQTT broker."""
print(f"Disconnected with result code {rc}")
if rc != 0:
print("Unexpected disconnection. Attempting to reconnect...")
# Create MQTT client
client = mqtt.Client(client_id=MQTT_CLIENT_ID)
client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)