Files
skrypty_narzedzia/node_exporter_installer.py
2025-05-27 13:58:30 +02:00

317 lines
11 KiB
Python
Raw 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
import bcrypt
# 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'
CONFIG_DIR = '/etc/node_exporter'
CONFIG_PATH = os.path.join(CONFIG_DIR, 'config.yml')
# 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 write_systemd_service(secured=False):
exec_line = f'{BIN_TARGET} --web.config.file="{CONFIG_PATH}"' if secured else BIN_TARGET
content = f"""[Unit]
Description=Node Exporter
Wants=network-online.target
After=network-online.target
[Service]
User={USER_NAME}
ExecStart={exec_line}
[Install]
WantedBy=default.target
"""
with open(SERVICE_FILE, "w") as f:
f.write(content)
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 jest już zainstalowany.")
response = input("Czy chcesz przeinstalować z konfiguracją secured (TLS + Basic Auth)? [t/N]: ")
if response.lower() == 't':
uninstall()
install()
setup_secured_config()
write_systemd_service(secured=True)
run_cmd(['systemctl', 'daemon-reexec'])
run_cmd(['systemctl', 'restart', 'node_exporter'])
print("✅ Node Exporter przeinstalowany z konfiguracją secured.")
else:
print(" Instalacja przerwana - bez zmian.")
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()
write_systemd_service(secured=False)
run_cmd(['systemctl', 'daemon-reload'])
run_cmd(['systemctl', 'enable', '--now', 'node_exporter'])
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_secured_config():
os.makedirs(CONFIG_DIR, exist_ok=True)
subprocess.run([
"openssl", "req", "-new", "-newkey", "rsa:4096", "-days", "3650", "-nodes", "-x509",
"-subj", "/C=PL/ST=X/L=X/O=secure/CN=localhost",
"-keyout", f"{CONFIG_DIR}/node_exporter.key",
"-out", f"{CONFIG_DIR}/node_exporter.crt"
], check=True)
config = """basic_auth_users:
root: $2y$10$SNr5iyJMvqiecOx6tXgDTuBpxyd40Byp2j.iBM5lR/oQnlpi8nAje
tls_server_config:
cert_file: node_exporter.crt
key_file: node_exporter.key
"""
with open(CONFIG_PATH, "w") as f:
f.write(config)
shutil.chown(CONFIG_DIR, user=USER_NAME, group=USER_NAME)
for f_name in os.listdir(CONFIG_DIR):
shutil.chown(os.path.join(CONFIG_DIR, f_name), user=USER_NAME, group=USER_NAME)
def change_password(user, password):
if not os.path.exists(CONFIG_PATH):
print("Brak pliku config.yml najpierw użyj --install-secured")
return
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
with open(CONFIG_PATH, 'r') as f:
lines = f.readlines()
new_lines = []
in_auth = False
user_updated = False
for line in lines:
if line.strip().startswith("basic_auth_users:"):
in_auth = True
new_lines.append(line)
continue
if in_auth and line.startswith(" ") and ":" in line:
existing_user = line.split(":")[0].strip()
if existing_user == user:
new_lines.append(f" {user}: {hashed}\n")
user_updated = True
else:
new_lines.append(line)
elif in_auth and not line.startswith(" "):
if not user_updated:
new_lines.append(f" {user}: {hashed}\n")
user_updated = True
in_auth = False
new_lines.append(line)
else:
new_lines.append(line)
if in_auth and not user_updated:
new_lines.append(f" {user}: {hashed}\n")
with open(CONFIG_PATH, "w") as f:
f.writelines(new_lines)
print(f"Zmieniono hasło dla użytkownika '{user}'")
def print_help():
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
node_exporter_manager.py --install-secured # Instalacja z TLS + basic auth (certyfikat + config.yml)
node_exporter_manager.py --set-password=user:haslo # Zmiana hasła w config.yml
""")
# ----------------- 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:
print_help()
sys.exit(1)
arg = sys.argv[1]
try:
if arg == '--install':
install()
elif arg == '--update':
update()
elif arg == '--uninstall':
uninstall()
elif arg == '--setup':
setup()
elif arg == '--install-secured':
install()
setup_secured_config()
elif arg.startswith('--set-password='):
user_pass = arg.split('=')[1]
if ':' not in user_pass:
print("Użyj formatu --set-password=user:haslo")
sys.exit(1)
user, passwd = user_pass.split(':', 1)
change_password(user, passwd)
else:
print_help()
sys.exit(1)
except Exception as e:
logging.error(f"Błąd krytyczny: {e}")
print(f"Wystąpił błąd: {e}")
sys.exit(1)