Converted to PyPy package
This commit is contained in:
parent
6e6f22f5d4
commit
4878866ada
15
.gitignore
vendored
15
.gitignore
vendored
@ -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
6
MANIFEST.in
Normal 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
|
@ -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
20
pyproject.toml
Normal 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
|
@ -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
73
run.sh
@ -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
43
setup.py
Executable 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,
|
||||
)
|
20
src/system2mqtt/collectors/__init__.py
Normal file
20
src/system2mqtt/collectors/__init__.py
Normal 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',
|
||||
]
|
@ -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)
|
Loading…
x
Reference in New Issue
Block a user