diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 86e606c..170833d 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -21,7 +21,9 @@ services: volumes: - sync1config:/config environment: - - SYNCTHING_PUBLIC_HOST=sync1 + - SYNCTHING_HOSTNAME=sync1 + - SYNCTHING_ADDRESS=tcp4://sync1 + - SYNCTHING_DATA_PATH=/data networks: - syncnet @@ -45,7 +47,9 @@ services: volumes: - sync2config:/config environment: - - SYNCTHING_PUBLIC_HOST=sync2 + - SYNCTHING_HOSTNAME=sync2 + - SYNCTHING_ADDRESS=tcp4://sync2 + - SYNCTHING_DATA_PATH=/data networks: - syncnet diff --git a/syncthing_monitor/__main__.py b/syncthing_monitor/__main__.py index 0f940cf..04d3f04 100644 --- a/syncthing_monitor/__main__.py +++ b/syncthing_monitor/__main__.py @@ -2,27 +2,35 @@ import os import sys import time +from retrying import retry + import syncthing_monitor.config_xml as xml from .etcd_client import EtcdClient from .syncthing_client import SyncthingClient +SHARED_FOLDER_ID = "data" +SHARED_FOLDER_LABEL = "syncthing_monitor_data" SYNCTHING_CONFIG_XML_PATH = '/config/config.xml' SYNCTHING_GUI_PORT = 8384 ETCD_PORT = 2379 def main(): - program = Program(os.getenv('SYNCTHING_PUBLIC_HOST'), - SYNCTHING_CONFIG_XML_PATH, "sync", SYNCTHING_GUI_PORT, - "etcd", ETCD_PORT) - program.start() + syncthing_mon = SyncthingMonitor(os.getenv('SYNCTHING_HOSTNAME'), + os.getenv('SYNCTHING_ADDRESS'), + os.getenv('SYNCTHING_DATA_PATH'), + SYNCTHING_CONFIG_XML_PATH, "sync", SYNCTHING_GUI_PORT, + "etcd", ETCD_PORT) + syncthing_mon.start() -class Program: - def __init__(self, syncthing_public_host, +class SyncthingMonitor: + def __init__(self, syncthing_public_host, syncthing_address, syncthing_data_path, syncthing_config_xml_path, syncthing_hostname, syncthing_gui_port, etcd_hostname, etcd_port): - self.syncthing_public_host = syncthing_public_host + self.syncthing_hostname = syncthing_public_host + self.syncthing_address = syncthing_address + self.syncthing_data_path = syncthing_data_path self.my_device_id = None self.syncthing = None self.etcd = EtcdClient(etcd_hostname, etcd_port) @@ -30,6 +38,7 @@ class Program: self.syncthing_hostname = syncthing_hostname self.syncthing_config_xml_path = syncthing_config_xml_path + @retry def initialize_syncthing(self): api_key = xml.parse_api_key(self.syncthing_config_xml_path) print("Found API Key: {0}".format(api_key)) @@ -41,20 +50,20 @@ class Program: self.syncthing = SyncthingClient(api_key, self.syncthing_hostname, self.syncthing_gui_port) + @retry def start(self): self.initialize_syncthing() self.my_device_id = self.syncthing.get_my_device_id() print("Found My Device ID: {0}".format(self.my_device_id)) - self.syncthing.patch_config() - self.syncthing.create_shared_folder("GXWxf-3zgnU", "SharedFolder", "/data", [{'id': self.my_device_id}]) - - if not self.syncthing.config_is_in_sync(): - self.syncthing.restart() + self.syncthing.disable_announce_discovery_and_relay() + self.syncthing.create_shared_folder(SHARED_FOLDER_ID, SHARED_FOLDER_LABEL, self.syncthing_data_path, + [{'id': self.my_device_id}]) + self.syncthing.sync_config() self.etcd.register_device_update_handler(self.update_devices) - self.etcd.add_device_to_cluster(self.my_device_id, self.syncthing_public_host) + self.etcd.add_device_to_cluster(self.my_device_id, self.syncthing_hostname, self.syncthing_address) self.loop() @@ -68,12 +77,11 @@ class Program: def update_devices(self, event): device_list = self.etcd.get_device_list() self.syncthing.add_devices(device_list) - self.syncthing.create_shared_folder("GXWxf-3zgnU", "SharedFolder", "/data", device_list) + self.syncthing.create_shared_folder(SHARED_FOLDER_ID, SHARED_FOLDER_LABEL, self.syncthing_data_path, + device_list) + self.syncthing.sync_config() self.syncthing.print_config() - if not self.syncthing.config_is_in_sync(): - self.syncthing.restart() - if __name__ == "__main__": main() diff --git a/syncthing_monitor/etcd_client.py b/syncthing_monitor/etcd_client.py index f455251..3aea89a 100644 --- a/syncthing_monitor/etcd_client.py +++ b/syncthing_monitor/etcd_client.py @@ -17,7 +17,7 @@ class EtcdClient: return json.loads(raw_value) @retry - def add_device_to_cluster(self, device_id, public_host): + def add_device_to_cluster(self, device_id, hostname, address): with self.etcd.lock('syncthing_monitor'): cluster_info = self.load_cluster_info() @@ -26,7 +26,8 @@ class EtcdClient: new_device = { 'id': device_id, - 'hostname': public_host + 'hostname': hostname, + 'address': address } cluster_info['devices'].append(new_device) diff --git a/syncthing_monitor/syncthing_client.py b/syncthing_monitor/syncthing_client.py index ec17827..9924e33 100644 --- a/syncthing_monitor/syncthing_client.py +++ b/syncthing_monitor/syncthing_client.py @@ -11,24 +11,30 @@ class SyncthingClient: self.host = host self.port = port self.headers = {'X-API-Key': self.api_key} + self.myID = None @retry def get_my_device_id(self): - response = requests.get(self.make_url("/rest/system/status"), headers=self.headers) - return json.loads(response.content)["myID"] + if self.myID is None: + response = self.get("/rest/system/status") + status = json.loads(response.content) + self.myID = status["myID"] + + return self.myID def add_devices(self, device_list): for device in device_list: self.add_device(device) def add_device(self, device): + device_address = device['address'] + if device['id'] == self.myID: + device_address = 'dynamic' + post_data = { "deviceID": device['id'], "name": device['hostname'], - "addresses": [ - "dynamic", - "tcp://{0}:22000".format(device['hostname']) - ], + "addresses": [device_address], "compression": "metadata", "certName": "", "introducer": False, @@ -43,9 +49,7 @@ class SyncthingClient: "pendingFolders": [], "maxRequestKiB": 0 } - response = requests.post(self.make_url("/rest/config/devices"), - headers=self.headers, data=json.dumps(post_data)) - + response = self.post("/rest/config/devices", json.dumps(post_data)) print("Attempt to add device {0} to syncthing: {1}".format(device, response.content)) def create_shared_folder(self, folder_id, label, path, devices): @@ -95,40 +99,52 @@ class SyncthingClient: } post_data['devices'].append(folder_device) - response = requests.post(self.make_url("/rest/config/folders"), - headers=self.headers, data=json.dumps(post_data)) - + response = self.post("/rest/config/folders", json.dumps(post_data)) print("Attempt to add shared folder to syncthing: {0}".format(response.content)) @retry def print_config(self): - response = requests.get(self.make_url("/rest/config/devices"), headers=self.headers) + response = self.get("/rest/config/devices") print("/rest/config/devices: {0}".format(response.content)) - response = requests.get(self.make_url("/rest/config/folders"), headers=self.headers) + + response = self.get("/rest/config/folders") print("/rest/config/folders: {0}".format(response.content)) - response = requests.get(self.make_url("/rest/config/options"), headers=self.headers) + + response = self.get("/rest/config/options") print("/rest/config/options: {0}".format(response.content)) + @retry def config_is_in_sync(self): - response = requests.get(self.make_url("/rest/config/insync"), headers=self.headers) + response = self.get("/rest/config/insync") return bool(json.loads(response.content)['configInSync']) + @retry def restart(self): - response = requests.post(self.make_url("/rest/system/restart"), headers=self.headers, data='') + response = self.post("/rest/system/restart", '') print("System reset: {0}".format(response.content)) + def sync_config(self): + if not self.config_is_in_sync(): + self.restart() - @retry - def patch_config(self): + def disable_announce_discovery_and_relay(self): config_patch = { "globalAnnounceEnabled": False, "localAnnounceEnabled": False, "relaysEnabled": False, "announceLANAddresses": False, } - response = requests.patch(self.make_url("/rest/config/options"), - headers=self.headers, data=json.dumps(config_patch)) + response = self.patch("/rest/config/options", json.dumps(config_patch)) print("Patched syncthing configuration: {0}".format(response.content)) def make_url(self, endpoint): return "http://{0}:{1}{2}".format(self.host, self.port, endpoint) + + def get(self, endpoint): + return requests.get(self.make_url(endpoint), headers=self.headers) + + def post(self, endpoint, data): + return requests.post(self.make_url(endpoint), headers=self.headers, data=data) + + def patch(self, endpoint, data): + return requests.patch(self.make_url(endpoint), headers=self.headers, data=data)