node_exporter_manager/node_exporter_manager.py
2025-05-06 08:52:11 +02:00

240 lines
8.2 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
import os
import sys
import subprocess
import requests
import tarfile
import shutil
import logging
import pwd
from pathlib import Path
# Stałe ścieżki i konfiguracja
BIN_TARGET = '/usr/local/bin/node_exporter'
SERVICE_FILE = '/etc/systemd/system/node_exporter.service'
LOG_FILE = '/var/log/node_exporter_installer.log'
USER_NAME = 'node_exporter'
USER_HOME = '/var/lib/node_exporter'
# Konfiguracja logowania
logging.basicConfig(
filename=LOG_FILE,
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s'
)
# ----------------- FUNKCJE POMOCNICZE -----------------
def run_cmd(cmd, check=True):
try:
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=check, text=True)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
logging.error(f"Błąd komendy: {cmd}{e.stderr}")
raise
def get_latest_version():
try:
r = requests.get('https://api.github.com/repos/prometheus/node_exporter/releases/latest', timeout=10)
r.raise_for_status()
return r.json()['tag_name'].lstrip('v'), r.json()
except requests.RequestException as e:
logging.error(f"Błąd pobierania wersji z GitHub: {e}")
raise
def get_local_version():
if Path(BIN_TARGET).exists():
try:
output = run_cmd([BIN_TARGET, '--version'])
for line in output.splitlines():
if "version" in line:
parts = line.split()
if len(parts) >= 3:
return parts[2]
except Exception as e:
logging.warning(f"Nie udało się odczytać wersji lokalnej: {e}")
return None
def download_and_extract(url, download_path='/tmp'):
filename = os.path.join(download_path, url.split('/')[-1])
try:
with requests.get(url, stream=True, timeout=30) as r:
r.raise_for_status()
with open(filename, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
except requests.RequestException as e:
logging.error(f"Błąd pobierania pliku: {e}")
raise
with tarfile.open(filename) as tar:
tar.extractall(path=download_path, filter='data')
return next(Path(download_path).glob('node_exporter-*'))
# ----------------- INSTALACJA I KONFIGURACJA -----------------
def install_binary(extracted_dir):
src = Path(extracted_dir) / 'node_exporter'
if Path(BIN_TARGET).exists():
shutil.copy(BIN_TARGET, BIN_TARGET + '.bak')
logging.info("Backup binarki zrobiony jako node_exporter.bak")
shutil.copy(src, BIN_TARGET)
os.chmod(BIN_TARGET, 0o755)
logging.info("Zainstalowano binarkę do /usr/local/bin")
def create_user():
try:
pwd.getpwnam(USER_NAME)
logging.info("Użytkownik node_exporter już istnieje.")
except KeyError:
run_cmd(['useradd', '--system', '--home', USER_HOME, '--shell', '/bin/false', USER_NAME])
logging.info("Utworzono użytkownika node_exporter")
home_path = Path(USER_HOME)
home_path.mkdir(parents=True, exist_ok=True)
shutil.chown(home_path, user=USER_NAME, group=USER_NAME)
logging.info(f"Utworzono katalog {USER_HOME} i przypisano właściciela.")
def setup_service():
service_content = f"""[Unit]
Description=Node Exporter
Wants=network-online.target
After=network-online.target
[Service]
User={USER_NAME}
Group={USER_NAME}
WorkingDirectory={USER_HOME}
ExecStart={BIN_TARGET}
Restart=on-failure
[Install]
WantedBy=default.target
"""
with open(SERVICE_FILE, 'w') as f:
f.write(service_content)
logging.info("Zapisano konfigurację usługi systemd")
run_cmd(['systemctl', 'daemon-reload'])
run_cmd(['systemctl', 'enable', '--now', 'node_exporter'])
logging.info("Włączono i uruchomiono usługę node_exporter")
def uninstall():
if Path(SERVICE_FILE).exists():
run_cmd(['systemctl', 'disable', '--now', 'node_exporter'])
os.remove(SERVICE_FILE)
logging.info("Usunięto plik usługi i zatrzymano node_exporter")
if Path(BIN_TARGET).exists():
os.remove(BIN_TARGET)
logging.info("Usunięto binarkę node_exporter")
try:
pwd.getpwnam(USER_NAME)
run_cmd(['userdel', USER_NAME])
logging.info("Usunięto użytkownika node_exporter")
except KeyError:
logging.info("Użytkownik już nie istnieje")
if Path(USER_HOME).exists():
shutil.rmtree(USER_HOME)
logging.info("Usunięto katalog /var/lib/node_exporter")
def install():
if Path(BIN_TARGET).exists():
print("Node Exporter już zainstalowany. Użyj --update.")
return
version, release = get_latest_version()
url = next(asset['browser_download_url'] for asset in release['assets'] if 'linux-amd64.tar.gz' in asset['browser_download_url'])
extracted = download_and_extract(url)
install_binary(extracted)
create_user()
setup_service()
logging.info("Instalacja zakończona")
def update():
local_version = get_local_version()
latest_version, release = get_latest_version()
if local_version == latest_version:
print(f"Node Exporter już aktualny ({local_version})")
return
print(f"Aktualizacja z {local_version} do {latest_version}...")
run_cmd(['systemctl', 'stop', 'node_exporter'])
url = next(asset['browser_download_url'] for asset in release['assets'] if 'linux-amd64.tar.gz' in asset['browser_download_url'])
extracted = download_and_extract(url)
install_binary(extracted)
run_cmd(['systemctl', 'start', 'node_exporter'])
logging.info(f"Zaktualizowano Node Exporter do wersji {latest_version}")
def setup():
source_path = Path(__file__).resolve()
target_path = Path('/usr/local/bin/node_exporter_manager.py')
if source_path == target_path:
print(" Skrypt już działa z docelowej lokalizacji.")
elif not target_path.exists():
shutil.copy(source_path, target_path)
target_path.chmod(0o755)
logging.info(f"Zainstalowano skrypt jako {target_path}")
print(f"✅ Skrypt zainstalowany w {target_path}")
else:
print(f" Skrypt już zainstalowany w {target_path}")
cron_line = f"15 3 * * * {target_path} --update >> /var/log/node_exporter_cron.log 2>&1"
try:
cron_result = run_cmd(['crontab', '-l'], check=False)
except Exception:
cron_result = ""
if cron_line not in cron_result:
with open('/tmp/node_exporter_cron', 'w') as f:
f.write(cron_result.strip() + '\n' + cron_line + '\n')
run_cmd(['crontab', '/tmp/node_exporter_cron'])
os.remove('/tmp/node_exporter_cron')
logging.info("Dodano wpis do crontaba")
print("✅ Zadanie cron dodane")
else:
print(" Wpis cron już istnieje.")
logrotate_path = '/etc/logrotate.d/node_exporter_manager'
logrotate_config = f"""{LOG_FILE} /var/log/node_exporter_cron.log {{
weekly
rotate 4
compress
missingok
notifempty
create 644 root root
}}
"""
with open(logrotate_path, 'w') as f:
f.write(logrotate_config)
logging.info("Skonfigurowano logrotate")
print(f"✅ Logrotate dodany w {logrotate_path}")
# ----------------- GŁÓWNY BLOK -----------------
if __name__ == '__main__':
if os.geteuid() != 0:
print("Ten skrypt musi być uruchomiony jako root.")
sys.exit(1)
if len(sys.argv) != 2 or sys.argv[1] not in ['--install', '--update', '--uninstall', '--setup']:
print("""
Użycie:
node_exporter_manager.py --install # Instaluje node_exporter i uruchamia usługę
node_exporter_manager.py --update # Aktualizuje node_exporter do najnowszej wersji (jeśli potrzeba)
node_exporter_manager.py --uninstall # Usuwa node_exporter, usługę, użytkownika
node_exporter_manager.py --setup # Instaluje ten skrypt do /usr/local/bin, dodaje CRON i logrotate
""")
sys.exit(1)
try:
{
'--install': install,
'--update': update,
'--uninstall': uninstall,
'--setup': setup
}[sys.argv[1]]()
except Exception as e:
logging.error(f"Błąd krytyczny: {e}")
print(f"Wystąpił błąd: {e}")
sys.exit(1)