1414# --clean - full cleanup: removes service, config, logs, metrics and dev dirs (safe to call repeatedly)
1515# ------------------------------------------------------------------------------
1616
17- AGENT_VERSION = "1.1.1 "
17+ AGENT_VERSION = "1.1.2 "
1818
1919import os , sys , json , time , yaml , uuid , hmac , psutil , socket , hashlib , logging , requests , platform , subprocess , shutil
2020from pathlib import Path
@@ -102,15 +102,57 @@ def get_domain_name():
102102 for line in f :
103103 if line .startswith ("search" ):
104104 parts = line .strip ().split ()
105- if len (parts ) > 1 and parts [1 ] != "." :
105+ if len (parts ) > 1 and parts [1 ] not in ( "." , "localdomain" ) :
106106 return parts [1 ]
107- domain = subprocess .check_output ("hostname -d" , shell = True ). decode ( ).strip ()
108- if domain and domain != "(none)" :
107+ domain = subprocess .check_output ("hostname -d" , shell = True , text = True ).strip ()
108+ if domain and domain not in ( "(none)" , "." , "localdomain" ) :
109109 return domain
110110 except Exception as e :
111111 logging .warning (f"Cannot determine domain_name: { e } " )
112112 return ""
113113
114+
115+ def get_cpu_name ():
116+ """
117+ Returns a detailed human-readable CPU model name with multiple fallbacks.
118+ Example: 'Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz'
119+ """
120+ import platform , subprocess
121+
122+ # 1. Try lscpu (most reliable)
123+ try :
124+ output = subprocess .check_output ("lscpu | grep 'Model name'" , shell = True , text = True )
125+ if ":" in output :
126+ name = output .split (":" , 1 )[1 ].strip ()
127+ if name :
128+ return name
129+ except Exception :
130+ pass
131+
132+ # 2. Try /proc/cpuinfo
133+ try :
134+ with open ("/proc/cpuinfo" , "r" ) as f :
135+ for line in f :
136+ if "model name" in line :
137+ return line .split (":" , 1 )[1 ].strip ()
138+ except Exception :
139+ pass
140+
141+ # 3. Try platform.processor()
142+ cpu = platform .processor ()
143+ if cpu and cpu .strip () and cpu .lower () != "unknown" and cpu .lower () != "x86_64" :
144+ return cpu .strip ()
145+
146+ # 4. Fallback: use uname
147+ try :
148+ return subprocess .check_output ("uname -p" , shell = True , text = True ).strip ()
149+ except Exception :
150+ pass
151+
152+ return "Unknown CPU"
153+
154+
155+
114156def ensure_files ():
115157 """
116158 Ensures that all required agent files and directories exist.
@@ -189,6 +231,7 @@ def register_agent(cfg, company_key):
189231 hardware_id = cfg ["hardware_id" ]
190232 headers = {
191233 "X-Company-Key" : company_key ,
234+ "X-Agent-Version" : AGENT_VERSION ,
192235 "Content-Type" : "application/json"
193236 }
194237 payload = {
@@ -331,7 +374,7 @@ def install_service():
331374Type=simple
332375User=nodesniff
333376Group=nodesniff
334- ExecStart=/usr/bin/python3 { AGENT_DEST_PATH }
377+ ExecStart=/usr/bin/python3 { AGENT_DEST_PATH } --daemon
335378Restart=on-failure
336379RestartSec=10
337380StandardOutput=journal
@@ -447,6 +490,7 @@ def collect_metrics(cfg):
447490 "cpu_freq_current" : int (cpu_freq .current ) if cpu_freq else None ,
448491 "cpu_freq_max" : int (cpu_freq .max ) if cpu_freq else None ,
449492 "cpu_temp" : cpu_temp ,
493+ "cpu_name" : get_cpu_name (),
450494 "memory_total_mb" : int (vmem .total / 1024 / 1024 ),
451495 "memory_used_mb" : int (vmem .used / 1024 / 1024 ),
452496 "memory_cached_mb" : int (getattr (vmem , "cached" , 0 ) / 1024 / 1024 ),
@@ -465,7 +509,7 @@ def collect_metrics(cfg):
465509 "cloud_region" : None ,
466510 "hardware_id" : hardware_id ,
467511 "extra_metrics" : extra_metrics ,
468- "timestamp" : int (time .time ())
512+ "timestamp" : int (time .time ())
469513 }
470514
471515def send_metrics (cfg , metrics ):
@@ -490,13 +534,17 @@ def send_metrics(cfg, metrics):
490534 elif r .status_code == 403 :
491535 print_error ("❌ Agent was removed via UI or token is invalid (HTTP 403)." )
492536 print_error ("You must re-register this agent to continue monitoring." )
493- # Remove config & token to force re-registration
494537 if os .path .exists (CONFIG_PATH ): os .remove (CONFIG_PATH )
495538 if os .path .exists (TOKEN_PATH ): os .remove (TOKEN_PATH )
496539 print_info ("Agent config and token removed, registration required on next start." )
497540 sys .exit (1 )
541+ elif r .status_code == 426 :
542+ print_error (f"❌ Agent version too old. Minimum required version: { r .json ().get ('detail' )} " )
543+ logging .error (f"Agent version too old. Response: { r .text } " )
544+ sys .exit (1 )
498545 else :
499546 logging .error (f"Send failed: { r .status_code } { r .text } " )
547+ sys .exit (1 )
500548 except Exception as e :
501549 logging .exception ("Error sending metrics" )
502550
@@ -563,6 +611,7 @@ def clean_agent():
563611 if "--clean" in sys .argv :
564612 clean_agent ()
565613 sys .exit (0 )
614+
566615 if "--service" in sys .argv :
567616 install_service ()
568617 sys .exit (0 )
@@ -571,16 +620,20 @@ def clean_agent():
571620 ensure_files ()
572621 cfg = load_config ()
573622
574- # Register the agent if not registered yet
575- if "api_token" not in cfg or "hmac_secret" not in cfg :
576- key = load_company_key ()
577- register_agent (cfg , key )
578- metrics = collect_metrics (cfg )
579- send_metrics (cfg , metrics )
580- print_ok ("Registration complete. You can now start nsagent in background with '&' (./nsagent.py &) or use '--service' to install as a systemd service." )
581- sys .exit (0 )
582-
583- # Check if machine-id in config matches the current host
623+ # --- registration mode (default) ---
624+ if "--daemon" not in sys .argv :
625+ if "api_token" not in cfg or "hmac_secret" not in cfg :
626+ key = load_company_key ()
627+ register_agent (cfg , key )
628+ metrics = collect_metrics (cfg )
629+ send_metrics (cfg , metrics )
630+ print_ok (
631+ "Registration complete. You can now start nsagent in background with '&' (./nsagent.py &) "
632+ "or use '--service' to install as a systemd service."
633+ )
634+ sys .exit (0 )
635+
636+ # --- validate machine-id before starting loop ---
584637 config_machine_id = cfg .get ("machine_id" )
585638 actual_machine_id = get_machine_id ()
586639 if not config_machine_id or not actual_machine_id :
@@ -590,11 +643,18 @@ def clean_agent():
590643 print_error ("Agent config was copied from another host (machine-id mismatch). Registration required!" )
591644 sys .exit (1 )
592645
593- # Main metrics loop
646+ # --- daemon mode (continuous metrics loop) ---
594647 interval = max (cfg .get ("interval_seconds" , 60 ), 10 )
595648 while True :
596- metrics = collect_metrics (cfg )
597- send_metrics (cfg , metrics )
598- time .sleep (interval )
649+ try :
650+ metrics = collect_metrics (cfg )
651+ send_metrics (cfg , metrics )
652+ time .sleep (interval )
653+ except Exception as e :
654+ logging .exception (f"Error in metrics loop: { e } " )
655+ time .sleep (interval )
656+
657+
658+
599659
600660
0 commit comments