You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							133 lines
						
					
					
						
							4.4 KiB
						
					
					
				
			
		
		
	
	
							133 lines
						
					
					
						
							4.4 KiB
						
					
					
				| import os
 | |
| import uuid
 | |
| import subprocess
 | |
| import requests
 | |
| import threading
 | |
| import json
 | |
| from urllib.parse import urlparse, urlunparse, urljoin
 | |
| from collections import namedtuple
 | |
| 
 | |
| from . import plugin
 | |
| # Check for shepherd.new file in edit conf dir. If there,
 | |
| # or if no shepherd.id file can be found, generate a new one. 
 | |
| # For now, also attempt to delete /var/lib/zerotier-one/identity.public and identity.secret
 | |
| # Once generated, if it was due to shepherd.new file, delete it.
 | |
| 
 | |
| 
 | |
| #Start new thread, and push ID and core config to api.shepherd.distreon.net/client/update
 | |
| 
 | |
| class UpdateManager():
 | |
|     def __init__(self):
 | |
|         pass
 | |
| 
 | |
| class SequenceUpdate():
 | |
|     Item = namedtuple('Item', ['sequence_number', 'content'])
 | |
|     def __init__(self):
 | |
|         self.items = []
 | |
|         self._sequence_count = 0
 | |
|         self._dirty = False
 | |
| 
 | |
|     def _next_sequence_number(self):
 | |
|         # TODO: need to establish a max sequence number, so that it can be compared to half
 | |
|         # that range and wrap around.
 | |
|         self._sequence_count +=1
 | |
|         return self._sequence_count
 | |
| 
 | |
|     def mark_as_dirty(self):
 | |
|         self._dirty = True
 | |
| 
 | |
|     def add_item(self, item):
 | |
|         self.items.append(self.Item(self._next_sequence_number(), item))
 | |
|         self.mark_as_dirty()
 | |
| 
 | |
|     def get_payload():
 | |
|         pass
 | |
|     def process_ack():
 | |
|         pass
 | |
| 
 | |
| client_id = None
 | |
| control_url = None
 | |
| api_key = None
 | |
| 
 | |
| def _update_job(core_config, plugin_config):
 | |
|     payload = {"client_id":client_id, "core_config":core_config,"plugin_config":plugin_config}
 | |
|     #json_string = json.dumps(payload)
 | |
|     try:
 | |
|         # Using the json arg rather than json.dumps ourselves automatically sets the Content-Type 
 | |
|         # header to application/json, which Flask expects to work correctly
 | |
|         r = requests.post(control_url, json=payload, auth=(client_id, api_key))
 | |
|     except requests.exceptions.ConnectionError:
 | |
|         raise
 | |
| 
 | |
| def generate_new_id(root_dir):
 | |
|     global client_id
 | |
|     with open(os.path.join(root_dir, "shepherd.id"), 'w+') as f:
 | |
|         new_id = uuid.uuid1()
 | |
|         client_id = str(new_id)
 | |
|         f.write(client_id)
 | |
|     generate_new_zerotier_id()
 | |
| 
 | |
| def get_config(config_dir):
 | |
|     return {}
 | |
| 
 | |
| def init_control(core_config, plugin_config):
 | |
|     global client_id
 | |
|     global control_url
 | |
|     global api_key
 | |
| 
 | |
|     # On init, need to be able to quickly return the cached shepherd control layer if necessary.
 | |
|     
 | |
|     # Create the /update endpoint structure
 | |
| 
 | |
|     root_dir = os.path.expanduser(core_config["root_dir"])
 | |
|     editconf_dir = os.path.dirname(os.path.expanduser(core_config["conf_edit_path"]))
 | |
| 
 | |
|     #Some weirdness with URL parsing means that by default most urls (like www.google.com)
 | |
|     # get treated as relative
 | |
|     # https://stackoverflow.com/questions/53816559/python-3-netloc-value-in-urllib-parse-is-empty-if-url-doesnt-have
 | |
| 
 | |
|     control_url = core_config["control_server"]
 | |
|     if "//" not in control_url:
 | |
|         control_url = "//"+control_url
 | |
|     control_url = urlunparse(urlparse(control_url)._replace(scheme="https"))
 | |
|     control_url = urljoin(control_url, "/client/update")
 | |
|     print(F"Control url is: {control_url}")
 | |
| 
 | |
|     api_key = core_config["api_key"]
 | |
| 
 | |
|     if os.path.isfile(os.path.join(editconf_dir, "shepherd.new")):
 | |
|         generate_new_id(root_dir)
 | |
|         os.remove(os.path.join(editconf_dir, "shepherd.new"))
 | |
|         print(F"Config hostname: {core_config['hostname']}")
 | |
|         if not (core_config["hostname"] == ""):
 | |
|             print("Attempting to change hostname")
 | |
|             subprocess.run(["raspi-config", "nonint", "do_hostname", core_config["hostname"]])
 | |
|     elif not os.path.isfile(os.path.join(root_dir, "shepherd.id")):
 | |
|         generate_new_id(root_dir)
 | |
|     else:
 | |
|         with open(os.path.join(root_dir, "shepherd.id"), 'r') as id_file:
 | |
|             client_id = id_file.readline().strip()
 | |
| 
 | |
|     print(F"Client ID is: {client_id}")
 | |
| 
 | |
|     control_thread = threading.Thread(target=_update_job, args=(core_config,plugin_config))
 | |
|     control_thread.start()
 | |
|         
 | |
| 
 | |
| def _post_logs_job():
 | |
|     logs = shepherd.plugin.plugin_functions["scout"].get_logs()
 | |
|     measurements = shepherd.plugin.plugin_functions["scout"].get_measurements()
 | |
| 
 | |
| 
 | |
|     payload = {"client_id":client_id, "logs":logs, "measurements":measurements}
 | |
|     
 | |
|     try:
 | |
|         r = requests.post(control_url, json=payload, auth=(client_id, api_key))
 | |
|     except requests.exceptions.ConnectionError:
 | |
|         pass
 | |
| 
 | |
| def post_logs():
 | |
|     post_logs_thread = threading.Thread(target=_post_logs_job, args=())
 | |
|     post_logs_thread.start()
 | |
| 
 |