ffpb.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. # -*- coding: utf-8 -*-
  2. from __future__ import print_function
  3. import willie
  4. import datetime
  5. from email.utils import mktime_tz
  6. import git
  7. import netaddr
  8. import json
  9. import urllib2
  10. import re
  11. import os
  12. import shelve
  13. import subprocess
  14. import time
  15. import dns.resolver,dns.reversename
  16. import socket
  17. import SocketServer
  18. import threading
  19. msgserver = None
  20. peers_repo = None
  21. monitored_nodes = None
  22. highscores = None
  23. alfred_method = None
  24. alfred_data = None
  25. alfred_update = datetime.datetime(1970,1,1,23,42)
  26. ffpb_resolver = dns.resolver.Resolver ()
  27. ffpb_resolver.nameservers = ['10.132.254.53']
  28. class MsgHandler(SocketServer.BaseRequestHandler):
  29. def handle(self):
  30. data = self.request.recv(2048).strip()
  31. sender = self._resolve_name (self.client_address[0])
  32. bot = self.server.bot
  33. if bot is None:
  34. print("ERROR: No bot in handle() :-(")
  35. return
  36. target = bot.config.core.owner
  37. if bot.config.has_section('ffpb'):
  38. is_public = data.lstrip().lower().startswith("public:")
  39. if is_public and not (bot.config.ffpb.msg_target_public is None):
  40. data = data[7:].lstrip()
  41. target = bot.config.ffpb.msg_target_public
  42. elif not (bot.config.ffpb.msg_target is None):
  43. target = bot.config.ffpb.msg_target
  44. bot.msg(target, "[{0}] {1}".format(sender, str(data)))
  45. def _resolve_name (self, ip):
  46. if ip.startswith ("127."):
  47. return "localhost"
  48. try:
  49. addr = dns.reversename.from_address (ip)
  50. return re.sub ("(.infra)?.ffpb.", "", str (ffpb_resolver.query (addr, "PTR")[0]))
  51. except dns.resolver.NXDOMAIN:
  52. return ip
  53. class ThreadingTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
  54. pass
  55. def setup(bot):
  56. global msgserver, peers_repo, alfred_method, highscores, monitored_nodes
  57. highscores = shelve.open('highscoredata', writeback=True)
  58. if not 'nodes' in highscores:
  59. highscores['nodes'] = 0
  60. highscores['nodes_ts'] = time.time()
  61. if not 'clients' in highscores:
  62. highscores['clients'] = 0
  63. highscores['clients_ts'] = time.time()
  64. monitored_nodes = shelve.open('monitorednodes', writeback=True)
  65. if not bot.config.has_section('ffpb'):
  66. return
  67. if not bot.config.ffpb.peers_directory is None:
  68. peers_repo = git.Repo(bot.config.ffpb.peers_directory)
  69. assert peers_repo.bare == False
  70. if int(bot.config.ffpb.msg_enable) == 1:
  71. host = "localhost"
  72. port = 2342
  73. if not bot.config.ffpb.msg_host is None: host = bot.config.ffpb.msg_host
  74. if not bot.config.ffpb.msg_port is None: port = int(bot.config.ffpb.msg_port)
  75. msgserver = ThreadingTCPServer((host,port), MsgHandler)
  76. msgserver.bot = bot
  77. ip, port = msgserver.server_address
  78. print("Messaging server listening on {}:{}".format(ip,port))
  79. msgserver_thread = threading.Thread(target=msgserver.serve_forever)
  80. msgserver_thread.daemon = True
  81. msgserver_thread.start()
  82. alfred_method = bot.config.ffpb.alfred_method
  83. ffpb_updatealfred(bot)
  84. def shutdown(bot):
  85. global msgserver, highscores, monitored_nodes
  86. if not highscores is None:
  87. highscores.sync()
  88. highscores.close()
  89. highscores = None
  90. if not monitored_nodes is None:
  91. monitored_nodes.sync()
  92. monitored_nodes.close()
  93. monitored_nodes = None
  94. if not msgserver is None:
  95. msgserver.shutdown()
  96. print("Closed messaging server.")
  97. msgserver = None
  98. @willie.module.commands("help")
  99. @willie.module.commands("hilfe")
  100. @willie.module.commands("man")
  101. def ffpb_help(bot, trigger):
  102. functions = {
  103. "!ping <knoten>": "Prüfe ob der Knoten erreichbar ist.",
  104. "!status": "Aktuellen Status des Netzwerks (insb. Anzahl Knoten und Clients) ausgegeben.",
  105. "!info <knoten>": "Allgemeine Information zu dem Knoten anzeigen.",
  106. "!link <knoten>": "MAC-Adresse und Link zur Status-Seite des Knotens anzeigen.",
  107. "!exec-on-peer <knoten> <kommando>": "Befehl auf dem Knoten ausführen (nur möglich bei eigenen Knoten oder als Admin, in beiden Fällen auch nur wenn der SSH-Key des Bots hinterlegt wurde)",
  108. }
  109. param = trigger.group(2)
  110. if param is None:
  111. bot.say("Funktionen: " + str.join(", ", sorted(functions.keys())))
  112. return
  113. if param.startswith("!"): param = param[1:]
  114. for fun in functions.keys():
  115. if fun.startswith("!" + param + " "):
  116. bot.say("Hilfe zu '" + fun + "': " + functions[fun])
  117. return
  118. bot.say("Allgemeine Hilfe gibt's mit !help - ohne Parameter.")
  119. def ffpb_findnode(name):
  120. if name is None or len(name) == 0:
  121. return None
  122. name = str(name).strip()
  123. # try to match MAC
  124. m = re.search("^([0-9a-fA-F][0-9a-fA-F]:){5}[0-9a-fA-F][0-9a-fA-F]$", name)
  125. if (not m is None):
  126. mac = m.group(0).lower()
  127. if mac in alfred_data:
  128. return alfred_data[mac]
  129. # try to find alias MAC
  130. for nodeid in alfred_data:
  131. node = alfred_data[nodeid]
  132. if "network" in node:
  133. if "mac" in node["network"] and node["network"]["mac"].lower() == mac:
  134. return node
  135. if "mesh_interfaces" in node["network"]:
  136. for mim in node["network"]["mesh_interfaces"]:
  137. if mim.lower() == mac:
  138. return node
  139. # look through the ALFRED peers
  140. possible_matches = []
  141. for nodeid in alfred_data:
  142. node = alfred_data[nodeid]
  143. if "hostname" in node and node["hostname"].lower() == name.lower():
  144. return node
  145. # still not found -> try peers_repo
  146. if not peers_repo is None:
  147. peer_name = None
  148. peer_mac = None
  149. peer_file = None
  150. for b in peers_repo.heads.master.commit.tree.blobs:
  151. if b.name.lower() == name.lower():
  152. peer_name = b.name
  153. peer_file = b.abspath
  154. break
  155. if (not peer_file is None) and os.path.exists(peer_file):
  156. peerfile = open(peer_file, "r")
  157. for line in peerfile:
  158. if line.startswith("# MAC:"):
  159. peer_mac = line[6:].strip()
  160. peerfile.close()
  161. if not (peer_mac is None):
  162. return { "hostname": peer_name, "network": { "addresses": [ mac2ipv6(peer_mac, "fdca:ffee:ff12:132:") ], "mac": peer_mac } }
  163. return None
  164. def ffpb_findnode_from_botparam(bot, name, ensure_recent_alfreddata = True):
  165. if (name is None or len(name) == 0):
  166. bot.reply("Grün.")
  167. return None
  168. if ensure_recent_alfreddata and alfred_data is None:
  169. bot.say("Informationen sind ausverkauft, kommen erst morgen wieder rein.")
  170. return None
  171. if ensure_recent_alfreddata and ffpb_alfred_data_outdated():
  172. bot.say("Ich habe gerade keine aktuellen Informationen, daher sage ich mal lieber nichts zu '" + name + "'.")
  173. return None
  174. node = ffpb_findnode(name)
  175. if node is None:
  176. bot.say("Kein Plan wer oder was mit '" + name + "' gemeint ist :(")
  177. return node
  178. def mac2ipv6(mac, prefix=None):
  179. result = str(netaddr.EUI(mac).ipv6_link_local())
  180. if (not prefix is None) and (result.startswith("fe80::")):
  181. result = prefix + result[6:]
  182. return result
  183. @willie.module.interval(30)
  184. def ffpb_updatealfred(bot):
  185. """Aktualisiere ALFRED-Daten"""
  186. global alfred_data, alfred_update
  187. if alfred_method is None or alfred_method == "None":
  188. return
  189. updated = None
  190. if alfred_method == "exec":
  191. rawdata = subprocess.check_output(['alfred-json', '-z', '-r', '158'])
  192. updated = datetime.datetime.now()
  193. elif alfred_method.startswith("http"):
  194. try:
  195. rawdata = urllib2.urlopen(alfred_method)
  196. except:
  197. print("Failed to download ALFRED data.")
  198. return
  199. updated = datetime.datetime.fromtimestamp(mktime_tz(rawdata.info().getdate_tz("Last-Modified")))
  200. else:
  201. print("Unknown ALFRED data method '", alfred_method, "', cannot load new data.", sep="")
  202. alfred_data = None
  203. return
  204. try:
  205. alfred_data = json.load(rawdata)
  206. #print("Fetched new ALFRED data:", len(alfred_data), "entries")
  207. alfred_update = updated
  208. except ValueError as e:
  209. print("Failed to parse ALFRED data: " + str(e))
  210. return
  211. def ffpb_alfred_data_outdated():
  212. timeout = datetime.datetime.now() - datetime.timedelta(minutes=5)
  213. is_outdated = timeout > alfred_update
  214. #print("ALFRED outdated? {0} (timeout={1} vs. lastupdate={2})".format(is_outdated, timeout, alfred_update))
  215. return is_outdated
  216. @willie.module.commands('debug-alfred')
  217. def ffpb_debug_alfred(bot, trigger):
  218. if alfred_data is None:
  219. bot.say("Keine ALFRED-Daten vorhanden.")
  220. else:
  221. bot.say("ALFRED Daten: count={0} lastupdate={1}".format(len(alfred_data), alfred_update))
  222. @willie.module.commands('alfred-data')
  223. def ffpb_peerdata(bot, trigger):
  224. if (not trigger.admin):
  225. bot.say('I wont leak (possibly) sensitive data to you.')
  226. return
  227. if (not trigger.is_privmsg) and (not trigger.nick in bot.ops[trigger.sender]):
  228. bot.say('Kein Keks? Keine Daten.')
  229. return
  230. target_name = trigger.group(2)
  231. node = ffpb_findnode_from_botparam(bot, target_name)
  232. if node is None: return
  233. for key in node:
  234. if key in [ 'hostname' ]: continue
  235. bot.say("{0}.{1} = {2}".format(node['hostname'], key, str(node[key])))
  236. @willie.module.commands('info')
  237. def ffpb_peerinfo(bot, trigger):
  238. target_name = trigger.group(2)
  239. node = ffpb_findnode_from_botparam(bot, target_name)
  240. if node is None: return
  241. info_mac = node["network"]["mac"]
  242. info_name = node["hostname"]
  243. info_hw = ""
  244. if "hardware" in node:
  245. if "model" in node["hardware"]:
  246. model = node["hardware"]["model"]
  247. info_hw = " model='" + model + "'"
  248. info_fw = ""
  249. info_update = ""
  250. if "software" in node:
  251. if "firmware" in node["software"]:
  252. fwinfo = str(node["software"]["firmware"]["release"]) if "release" in node["software"]["firmware"] else "unknown"
  253. info_fw = " firmware=" + fwinfo
  254. if "autoupdater" in node["software"]:
  255. autoupdater = node["software"]["autoupdater"]["branch"] if node["software"]["autoupdater"]["enabled"] else "off"
  256. info_update = " (autoupdater="+autoupdater+")"
  257. info_uptime = ""
  258. if "statistics" in node and "uptime" in node["statistics"]:
  259. u = int(float(node["statistics"]["uptime"]))
  260. d, r1 = divmod(int(float(node["statistics"]["uptime"])), 86400)
  261. h, r2 = divmod(r1, 3600)
  262. m, s = divmod(r2, 60)
  263. if d > 0:
  264. info_uptime = ' up {0}d {1}h'.format(d,h)
  265. elif h > 0:
  266. info_uptime = ' up {0}h {1}m'.format(h,m)
  267. else:
  268. info_uptime = ' up {0}m'.format(m)
  269. bot.say('[{1}]{2}{3}{4}{5}'.format(info_mac, info_name, info_hw, info_fw, info_update, info_uptime))
  270. @willie.module.commands('link')
  271. def ffpb_peerlink(bot, trigger):
  272. target_name = trigger.group(2)
  273. node = ffpb_findnode_from_botparam(bot, target_name)
  274. if node is None: return
  275. info_mac = node["network"]["mac"]
  276. info_name = node["hostname"]
  277. info_v6 = mac2ipv6(info_mac, 'fdca:ffee:ff12:132:')
  278. bot.say('[{1}] mac {0} -> http://[{2}]/'.format(info_mac, info_name, info_v6))
  279. @willie.module.interval(60)
  280. def ffpb_updatepeers(bot):
  281. """Aktualisiere die Knotenliste und melde das Diff"""
  282. if peers_repo is None:
  283. print('WARNING: peers_repo is None')
  284. return
  285. old_head = peers_repo.head.commit
  286. peers_repo.remotes.origin.pull()
  287. new_head = peers_repo.head.commit
  288. if new_head != old_head:
  289. print('git pull: from ' + str(old_head) + ' to ' + str(new_head))
  290. added = []
  291. changed = []
  292. renamed = []
  293. deleted = []
  294. for f in old_head.diff(new_head):
  295. if f.new_file:
  296. added.append(f.b_blob.name)
  297. elif f.deleted_file:
  298. deleted.append(f.a_blob.name)
  299. elif f.renamed:
  300. renamed.append([f.rename_from, f.rename_to])
  301. else:
  302. changed.append(f.a_blob.name)
  303. response = "Knoten-Update (VPN +{0} %{1} -{2}): ".format(len(added), len(renamed)+len(changed), len(deleted))
  304. for f in added:
  305. response += " +'{}'".format(f)
  306. for f in changed:
  307. response += " %'{}'".format(f)
  308. for f in renamed:
  309. response += " '{}'->'{}'".format(f[0],f[1])
  310. for f in deleted:
  311. response += " -'{}'".format(f)
  312. bot.msg(bot.config.ffpb.msg_target, response)
  313. def ffpb_fetch_stats(bot, url, memoryid):
  314. response = urllib2.urlopen(url)
  315. data = json.load(response)
  316. nodes_active = 0
  317. nodes_total = 0
  318. clients_count = 0
  319. for node in data['nodes']:
  320. if node['flags']['gateway'] or node['flags']['client']:
  321. continue
  322. nodes_total += 1
  323. if node['flags']['online']:
  324. nodes_active += 1
  325. for link in data['links']:
  326. if link['type'] == 'client':
  327. clients_count += 1
  328. if not memoryid in bot.memory:
  329. bot.memory[memoryid] = { }
  330. stats = bot.memory[memoryid]
  331. stats["fetchtime"] = time.time()
  332. stats["nodes_active"] = nodes_active
  333. stats["nodes_total"] = nodes_total
  334. stats["clients"] = clients_count
  335. return (nodes_active, nodes_total, clients_count)
  336. @willie.module.interval(15)
  337. def ffpb_get_stats(bot):
  338. (nodes_active, nodes_total, clients_count) = ffpb_fetch_stats(bot, 'http://map.paderborn.freifunk.net/nodes.json', 'ffpb_stats')
  339. highscore_changed = False
  340. if nodes_active > highscores['nodes']:
  341. highscores['nodes'] = nodes_active
  342. highscores['nodes_ts'] = time.time()
  343. highscore_changed = True
  344. if clients_count > highscores['clients']:
  345. highscores['clients'] = clients_count
  346. highscores['clients_ts'] = time.time()
  347. highscore_changed = True
  348. if highscore_changed:
  349. print('HIGHSCORE changed: {0} nodes ({1}), {2} clients ({3})'.format(highscores['nodes'], highscores['nodes_ts'], highscores['clients'], highscores['clients_ts']))
  350. if not (bot.config.ffpb.msg_target is None):
  351. action_msg = 'notiert sich den neuen Highscore: {0} Knoten ({1}), {2} Clients ({3})'.format(highscores['nodes'], pretty_date(int(highscores['nodes_ts'])), highscores['clients'], pretty_date(int(highscores['clients_ts'])))
  352. action_target = bot.config.ffpb.msg_target
  353. if (not bot.config.ffpb.msg_target_public is None):
  354. action_target = bot.config.ffpb.msg_target_public
  355. bot.msg(action_target, '\x01ACTION %s\x01' % action_msg)
  356. @willie.module.commands('status')
  357. def ffpb_status(bot, trigger):
  358. """Status des FFPB-Netzes: Anzahl (aktiver) Knoten + Clients"""
  359. stats = bot.memory['ffpb_stats'] if 'ffpb_stats' in bot.memory else None
  360. if stats is None:
  361. bot.say('Uff, kein Plan wo der Zettel ist. Fragst du später nochmal?')
  362. return
  363. bot.say('Es sind {0} Knoten und ca. {1} Clients online.'.format(stats["nodes_active"], stats["clients"]))
  364. def pretty_date(time=False):
  365. """
  366. Get a datetime object or a int() Epoch timestamp and return a
  367. pretty string like 'an hour ago', 'Yesterday', '3 months ago',
  368. 'just now', etc
  369. """
  370. from datetime import datetime
  371. now = datetime.now()
  372. compare = None
  373. if type(time) is int:
  374. compare = datetime.fromtimestamp(time)
  375. elif type(time) is float:
  376. compare = datetime.fromtimestamp(int(time))
  377. elif isinstance(time,datetime):
  378. compare = time
  379. elif not time:
  380. compare = now
  381. diff = now - compare
  382. second_diff = diff.seconds
  383. day_diff = diff.days
  384. if day_diff < 0:
  385. return ''
  386. if day_diff == 0:
  387. if second_diff < 10:
  388. return "gerade eben"
  389. if second_diff < 60:
  390. return "vor " + str(second_diff) + " Sekunden"
  391. if second_diff < 120:
  392. return "vor einer Minute"
  393. if second_diff < 3600:
  394. return "vor " + str(second_diff / 60) + " Minuten"
  395. if second_diff < 7200:
  396. return "vor einer Stunde"
  397. if second_diff < 86400:
  398. return "vor " + str(second_diff / 3600) + " Stunden"
  399. if day_diff == 1:
  400. return "gestern"
  401. if day_diff < 7:
  402. return "vor " + str(day_diff) + " Tagen"
  403. return "am " + compare.strftime('%d.%m.%Y um %H:%M Uhr')
  404. @willie.module.commands('highscore')
  405. def ffpb_highscore(bot, trigger):
  406. bot.say('Highscore: {0} Knoten ({1}), {2} Clients ({3})'.format(
  407. highscores['nodes'], pretty_date(int(highscores['nodes_ts'])),
  408. highscores['clients'], pretty_date(int(highscores['clients_ts']))))
  409. @willie.module.commands('rollout-status')
  410. def ffpb_rolloutstatus(bot, trigger):
  411. result = { }
  412. for branch in [ 'stable', 'testing' ]:
  413. result[branch] = None
  414. skipped = 0
  415. if (not (trigger.admin and trigger.is_privmsg)) and (not trigger.nick in bot.ops[trigger.sender]):
  416. bot.say('Geh zur dunklen Seite, die haben Kekse - ohne Keks kein Rollout-Status.')
  417. return
  418. expected_release = trigger.group(2)
  419. if expected_release is None or len(expected_release) == 0:
  420. bot.say('Von welcher Firmware denn?')
  421. return
  422. for nodeid in alfred_data:
  423. item = alfred_data[nodeid]
  424. if (not 'software' in item) or (not 'firmware' in item['software']) or (not 'autoupdater' in item['software']):
  425. skipped+=1
  426. continue
  427. release = item['software']['firmware']['release']
  428. branch = item['software']['autoupdater']['branch']
  429. enabled = item['software']['autoupdater']['enabled']
  430. if not branch in result or result[branch] is None:
  431. result[branch] = { 'auto_count': 0, 'auto_not': 0, 'manual_count': 0, 'manual_not': 0, 'total': 0 }
  432. result[branch]['total'] += 1
  433. match = 'count' if release == expected_release else 'not'
  434. mode = 'auto' if enabled else 'manual'
  435. result[branch][mode+'_'+match] += 1
  436. output = "Rollout von '{0}':".format(expected_release)
  437. for branch in result:
  438. auto_count = result[branch]['auto_count']
  439. auto_total = auto_count + result[branch]['auto_not']
  440. manual_count = result[branch]['manual_count']
  441. manual_total = manual_count + result[branch]['manual_not']
  442. bot.say("Rollout von '{0}': {1} = {2}/{3} per Auto-Update, {4}/{5} manuell".format(expected_release, branch, auto_count, auto_total, manual_count, manual_total))
  443. if skipped > 0:
  444. bot.say("Rollout von '{0}': {1} Knoten unklar".format(expected_release, skipped))
  445. @willie.module.commands('ping')
  446. def ffpb_ping(bot, trigger=None, target_name=None):
  447. """Ping FFPB-Knoten"""
  448. if target_name is None: target_name = trigger.group(2)
  449. node = ffpb_findnode_from_botparam(bot, target_name, ensure_recent_alfreddata=False)
  450. if node is None: return None
  451. target = [x for x in node["network"]["addresses"] if not x.lower().startswith("fe80:")][0]
  452. target_alias = node["hostname"]
  453. print("ping '", target , '"', sep='')
  454. result = os.system('ping6 -c 2 -W 1 ' + target + ' 2>/dev/null')
  455. if result == 0:
  456. if not bot is None: bot.say('Knoten "' + target_alias + '" antwortet \o/')
  457. return True
  458. elif result == 1 or result == 256:
  459. if not bot is None: bot.say('Keine Antwort von "' + target_alias + '" :-(')
  460. return False
  461. else:
  462. if not bot is None: bot.say('Uh oh, irgendwas ist kaputt. Chef, ping result = ' + str(result) + ' - darf ich das essen?')
  463. return None
  464. @willie.module.interval(3*60)
  465. def ffpb_monitor_ping(bot):
  466. notify_target = bot.config.core.owner
  467. if (not bot.config.ffpb.msg_target is None):
  468. notify_target = bot.config.ffpb.msg_target
  469. for node in monitored_nodes:
  470. added = monitored_nodes[node][0]
  471. last_status = monitored_nodes[node][1]
  472. last_check = monitored_nodes[node][2]
  473. current_status = ffpb_ping(bot=None, target_name=node)
  474. monitored_nodes[node] = ( added, current_status, time.time() )
  475. print("Monitoring ({0}) {1} (last: {2} at {3})".format(node, current_status, last_status, time.strftime('%Y-%m-%d %H:%M', time.localtime(last_check))))
  476. if last_status != current_status and (last_status or current_status):
  477. if last_check is None:
  478. # erster Check, keine Ausgabe
  479. continue
  480. if current_status == True:
  481. bot.msg(notify_target, 'Monitoring: Knoten \'{0}\' pingt wieder (zuletzt {1})'.format(node, pretty_date(last_check)))
  482. else:
  483. bot.msg(notify_target, 'Monitoring: Knoten \'{0}\' DOWN'.format(node))
  484. @willie.module.commands('monitor')
  485. def ffpb_monitor(bot, trigger):
  486. if not trigger.admin:
  487. bot.say('Ich ping hier nicht für jeden durch die Weltgeschichte.')
  488. return
  489. if trigger.group(2) is None or len(trigger.group(2)) == 0:
  490. bot.say('Das Monitoring sagt du hast doofe Ohren.')
  491. return
  492. cmd = trigger.group(3)
  493. node = trigger.group(4)
  494. if not node is None: node = str(node)
  495. if cmd == "add":
  496. if node in monitored_nodes:
  497. bot.say('Knoten \'{0}\' wird bereits gemonitored.'.format(node))
  498. return
  499. monitored_nodes[node] = ( trigger.sender, None, None )
  500. bot.say('Knoten \'{0}\' wird jetzt ganz genau beobachtet.'.format(node))
  501. return
  502. if cmd == "del":
  503. if not node in monitored_nodes:
  504. bot.say('Knoten \'{0}\' war gar nicht im Monitoring?!?'.format(node))
  505. return
  506. del monitored_nodes[node]
  507. bot.say('Okidoki, \'{0}\' war mir sowieso egal.'.format(node))
  508. return
  509. if cmd == "info":
  510. if node in monitored_nodes:
  511. info = monitored_nodes[node]
  512. bot.say('Knoten \'{0}\' wurde zuletzt {1} gepingt (Ergebnis: {2}) - der Auftrag kam von {3}'.format(node, pretty_date(info[2]) if not info[2] is None else "^W noch nie", info[1], info[0]))
  513. else:
  514. bot.say('Knoten \'{0}\' ist nicht im Monitoring.'.format(node))
  515. return
  516. if cmd == "list":
  517. nodes = ""
  518. for node in monitored_nodes:
  519. nodes = nodes + " " + node
  520. bot.say('Monitoring aktiv für:' + nodes)
  521. return
  522. if cmd == "help":
  523. bot.say('Entweder "!monitor list" oder "!monitor {add|del|info} <node>"')
  524. return
  525. bot.say('Mit "' + str(cmd) + '" kann ich nix anfangen, probier doch mal "!monitor help".')
  526. @willie.module.commands('exec-on-peer')
  527. def ffpb_remoteexec(bot, trigger):
  528. """Remote Execution fuer FFPB_Knoten"""
  529. bot_params = trigger.group(2).split(' ',1)
  530. if len(bot_params) != 2:
  531. bot.say('Wenn du nicht sagst wo mach ich remote execution bei dir!')
  532. bot.say('Tipp: !exec-on-peer <peer> <cmd>')
  533. return
  534. target_name = bot_params[0]
  535. target_cmd = bot_params[1]
  536. if not trigger.admin:
  537. bot.say('I can haz sudo?')
  538. return
  539. if trigger.is_privmsg:
  540. bot.say('Bitte per Channel.')
  541. return
  542. if not trigger.nick in bot.ops[trigger.sender]:
  543. bot.say('Geh weg.')
  544. return
  545. node = ffpb_findnode_from_botparam(bot, target_name, ensure_recent_alfreddata=False)
  546. if node is None: return
  547. target = [x for x in node["network"]["addresses"] if not x.lower().startswith("fe80:")][0]
  548. target_alias = node["hostname"]
  549. cmd = 'ssh -6 -l root ' + target + ' -- "' + target_cmd + '"'
  550. print("REMOTE EXEC = " + cmd)
  551. try:
  552. result = subprocess.check_output(['ssh', '-6n', '-l', 'root', '-o', 'BatchMode=yes', '-o','StrictHostKeyChecking=no', target, target_cmd], stderr=subprocess.STDOUT, shell=False)
  553. lines = str(result).splitlines()
  554. if len(lines) == 0:
  555. bot.say('exec-on-peer(' + target_alias + '): No output')
  556. return
  557. msg = 'exec-on-peer(' + target_alias + '): ' + str(len(lines)) + ' Zeilen'
  558. if len(lines) > 8:
  559. msg += ' (zeige max. 8)'
  560. bot.say(msg + ':')
  561. for line in lines[0:8]:
  562. bot.say(line)
  563. except subprocess.CalledProcessError as e:
  564. bot.say('Fehler '+str(e.returncode)+' bei exec-on-peer('+target_alias+'): ' + e.output)