def apiPlaceOrderExtension(session, shopurl, bridgeid): print("# apiPlaceOrderExtension") url = "{0}/api/v1/public/tor_bridges/{1}/extend/".format(shopurl, bridgeid) try: response = session.post(url) except Exception as e: raise BlitzError("failed HTTP request", {'url': url}, e) if response.status_code == 420: raise BlitzError("forwarding this address was rejected", {'status_code': response.status_code}) if response.status_code != 200 and response.status_code != 201: raise BlitzError("failed HTTP code", {'status_code': response.status_code}) # parse & validate data print("# parse") try: jData = json.loads(response.content) if len(jData['po_id']) == 0: print("error='MISSING ID'") return except Exception as e: raise BlitzError("failed JSON parsing", {'content': response.content}, e) return jData['po_id']
def apiPlaceOrderNew(session, shopurl, hostid, toraddressWithPort): print("# apiPlaceOrderNew") url = "{0}/api/v1/public/order/".format(shopurl) postData = { 'product': "tor_bridge", 'host_id': hostid, 'tos_accepted': True, 'comment': 'RaspiBlitz', 'target': toraddressWithPort, 'public_key': '' } try: response = session.post(url, data=postData) except Exception as e: raise BlitzError("failed HTTP request", {'url': url}, e) if response.status_code == 420: raise BlitzError("forwarding this address was rejected", {'status_code': response.status_code}) if response.status_code != 201: raise BlitzError("failed HTTP code", {'status_code': response.status_code}) # parse & validate data try: jData = json.loads(response.content) if len(jData['id']) == 0: print("error='MISSING ID'") return except Exception as e: raise BlitzError("failed JSON parsing", {'status_code': response.status_code}, e) return jData['id']
def apiGetOrder(session, shopurl, orderid) -> dict: print("# apiGetOrder") # make HTTP request url = "{0}/api/v1/public/pos/{1}/".format(shopurl, orderid) try: response = session.get(url) except Exception as e: raise BlitzError("failed HTTP request", {'url': url}, e) if response.status_code != 200: raise BlitzError("failed HTTP code", {'status_code': response.status_code}) # parse & validate data try: jData = json.loads(response.content) if len(jData['item_details']) == 0: raise BlitzError("missing item", {'content': response.content}) if len(jData['ln_invoices']) > 1: raise BlitzError("more than one invoice", {'content': response.content}) except Exception as e: raise BlitzError("failed JSON parsing", {'content': response.content}, e) return jData
def apiGetHosts(session, shopurl): print("# apiGetHosts") hosts = [] # make HTTP request url = "{0}/api/v1/public/hosts/?is_testnet={1}".format( shopurl, int(is_testnet)) try: response = session.get(url) except Exception as e: raise BlitzError("failed HTTP request", {'url': url}, e) if response.status_code != 200: raise BlitzError("failed HTTP code", {'status_code': response.status_code}) # parse & validate data try: jData = json.loads(response.content) except Exception as e: raise BlitzError("failed JSON parsing", {'content': response.content}, e) if not isinstance(jData, list): raise BlitzError("hosts not list", {'content': response.content}) for idx, hostEntry in enumerate(jData): try: # ignore if not offering tor bridge if not hostEntry['offers_tor_bridges']: continue # ignore if duration is less than an hour if hostEntry['tor_bridge_duration'] < 3600: continue # add duration per hour value hostEntry['tor_bridge_duration_hours'] = math.floor( hostEntry['tor_bridge_duration'] / 3600) # ignore if prices are negative or below one sat (maybe msats later) if hostEntry['tor_bridge_price_initial'] < 1000: continue if hostEntry['tor_bridge_price_extension'] < 1000: continue # add price in sats hostEntry['tor_bridge_price_initial_sats'] = math.ceil( hostEntry['tor_bridge_price_initial'] / 1000) hostEntry['tor_bridge_price_extension_sats'] = math.ceil( hostEntry['tor_bridge_price_extension'] / 1000) # ignore name is less then 3 chars if len(hostEntry['name']) < 3: continue # ignore id with zero value if len(hostEntry['id']) < 1: continue # shorten names to 20 chars max hostEntry['name'] = hostEntry['name'][:20] except Exception as e: raise BlitzError("failed host entry pasring", hostEntry, e) hosts.append(hostEntry) print("# found {0} valid torbridge hosts".format(len(hosts))) return hosts
def duckdns_update(domain, token, ip): print("# duckDNS update IP API call for {0}".format(domain)) # make HTTP request url = "https://www.duckdns.org/update?domains={0}&token={1}&ip={2}".format(domain.split('.')[0], token, ip) print("# calling URL: {0}".format(url)) try: response = session.get(url) if response.status_code != 200: raise BlitzError("failed HTTP code", str(response.status_code)) print("# response-code: {0}".format(response.status_code)) except Exception as e: raise BlitzError("failed HTTP request", url, e) return response.content
def subscription_new(): # check parameters try: if len(sys.argv) <= 5: raise BlitzError("incorrect parameters", "") except Exception as e: handleException(e) ip = sys.argv[2] dnsservice_type = sys.argv[3] dnsservice_id = sys.argv[4] dnsservice_token = sys.argv[5] if len(sys.argv) <= 6: target = "ip&tor" else: target = sys.argv[6] # create the subscription try: subscription = subscriptions_new(ip, dnsservice_type, dnsservice_id, dnsservice_token, target) # output json ordered bridge print(json.dumps(subscription, indent=2)) sys.exit() except Exception as e: handleException(e)
def shop_order(): # check parameters try: if len(sys.argv) <= 8: raise BlitzError("incorrect parameters") except Exception as e: handleException(e) shopurl = sys.argv[2] servicename = sys.argv[3] hostid = sys.argv[4] toraddress = sys.argv[5] duration = sys.argv[6] msatsFirst = sys.argv[7] msatsNext = sys.argv[8] if len(sys.argv) >= 10: description = sys.argv[9] else: description = "" # get data try: subscription = shopOrder(shopurl, hostid, servicename, toraddress, duration, msatsFirst, msatsNext, description) # output json ordered bridge print(json.dumps(subscription, indent=2)) sys.exit() except Exception as e: handleException(e)
def subscription_cancel(): # check parameters try: if len(sys.argv) <= 2: raise BlitzError("incorrect parameters") except Exception as e: handleException(e) subscriptionID = sys.argv[2] try: os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE)) subs = toml.load(SUBSCRIPTIONS_FILE) newList = [] for idx, sub in enumerate(subs['subscriptions_ip2tor']): if sub['id'] != subscriptionID: newList.append(sub) subs['subscriptions_ip2tor'] = newList # persist change with open(SUBSCRIPTIONS_FILE, 'w') as writer: writer.write(toml.dumps(subs)) writer.close() print(json.dumps(subs, indent=2)) except Exception as e: handleException(e)
def subscription_by_service(): # check parameters try: if len(sys.argv) <= 2: raise BlitzError("incorrect parameters") except Exception as e: handleException(e) service_name = sys.argv[2] try: if os.path.isfile(SUBSCRIPTIONS_FILE): os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE)) subs = toml.load(SUBSCRIPTIONS_FILE) for idx, sub in enumerate(subs['subscriptions_ip2tor']): if sub['active'] and sub['name'] == service_name: print("type='{0}'".format(sub['type'])) print("ip='{0}'".format(sub['ip'])) print("port='{0}'".format(sub['port'])) print("tor='{0}'".format(sub['tor'])) sys.exit(0) print("error='not found'") except Exception as e: handleException(e) sys.exit(1)
def ip_by_tor(): # check parameters try: if len(sys.argv) <= 2: raise BlitzError("incorrect parameters") except Exception as e: handleException(e) onion = sys.argv[2] try: if os.path.isfile(SUBSCRIPTIONS_FILE): subs = toml.load(SUBSCRIPTIONS_FILE) for idx, sub in enumerate(subs['subscriptions_ip2tor']): if sub['active'] and (sub['tor'] == onion or sub['tor'].split(":")[0] == onion): print("id='{0}'".format(sub['id'])) print("type='{0}'".format(sub['type'])) print("ip='{0}'".format(sub['ip'])) print("port='{0}'".format(sub['port'])) print("tor='{0}'".format(sub['tor'])) sys.exit(0) print("error='not found'") except Exception as e: handleException(e) sys.exit(1)
def subscriptions_cancel(s_id): os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE)) subs = toml.load(SUBSCRIPTIONS_FILE) new_list = [] removed_cert = None for idx, sub in enumerate(subs['subscriptions_letsencrypt']): if sub['id'] != s_id: new_list.append(sub) else: removed_cert = sub subs['subscriptions_letsencrypt'] = new_list # run the ACME script to remove cert if removed_cert: acme_result = subprocess.Popen( ["/home/admin/config.scripts/bonus.letsencrypt.sh", "remove-cert", removed_cert['id'], removed_cert['target']], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf8') out, err = acme_result.communicate() if out.find("error=") > -1: time.sleep(6) raise BlitzError("letsencrypt acme failed", out) # persist change with open(SUBSCRIPTIONS_FILE, 'w') as writer: writer.write(toml.dumps(subs)) writer.close() print(json.dumps(subs, indent=2))
def get_domain_by_ip(ip): # does subscriptin file exists if Path(SUBSCRIPTIONS_FILE).is_file(): subs = toml.load(SUBSCRIPTIONS_FILE) else: raise BlitzError("no match") # section with letsencrypt subs exists if "subscriptions_letsencrypt" not in subs: raise BlitzError("no match") # go thru subscription and check of a match for idx, sub in enumerate(subs['subscriptions_letsencrypt']): # if IP is a direct match if sub['ip'] == ip: return sub['id'] # if IP is a dynamicIP - check with the publicIP from the config if sub['ip'] == "dyndns": if cfg.public_ip == ip: return sub['id'] raise BlitzError("no match")
def apiGetBridgeStatus(session, shopurl, bridgeid): print("# apiGetBridgeStatus") # make HTTP request url = "{0}/api/v1/public/tor_bridges/{1}/".format(shopurl, bridgeid) try: response = session.get(url) except Exception as e: raise BlitzError("failed HTTP request", {'url': url}, e) if response.status_code != 200: raise BlitzError("failed HTTP code", {'status_code': response.status_code}) # parse & validate data try: jData = json.loads(response.content) if len(jData['id']) == 0: raise BlitzError("missing id", {'content': response.content}) except Exception as e: raise BlitzError("failed JSON parsing", {'content': response.content}, e) return jData
def subscription_cancel(): # check parameters try: if len(sys.argv) <= 2: raise BlitzError("incorrect parameters", "") except Exception as e: handleException(e) subscription_id = sys.argv[2] try: subscriptions_cancel(subscription_id) except Exception as e: handleException(e)
def lndPayInvoice(lnInvoiceString): try: # call LND GRPC API macaroon = codecs.encode(open(LND_ADMIN_MACAROON_PATH, 'rb').read(), 'hex') os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA' cert = open(LND_TLS_PATH, 'rb').read() ssl_creds = grpc.ssl_channel_credentials(cert) channel = grpc.secure_channel("{0}:10009".format(LND_IP), ssl_creds) stub = rpcstub.LightningStub(channel) request = lnrpc.SendRequest( payment_request=lnInvoiceString, ) response = stub.SendPaymentSync(request, metadata=[('macaroon', macaroon)]) # validate results if len(response.payment_error) > 0: raise BlitzError(response.payment_error, {'invoice': lnInvoiceString}) except Exception as e: raise BlitzError("payment failed", {'invoice': lnInvoiceString}, e) return response
def lndDecodeInvoice(lnInvoiceString): try: # call LND GRPC API macaroon = codecs.encode(open(LND_ADMIN_MACAROON_PATH, 'rb').read(), 'hex') os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA' cert = open(LND_TLS_PATH, 'rb').read() ssl_creds = grpc.ssl_channel_credentials(cert) channel = grpc.secure_channel("{0}:10009".format(LND_IP), ssl_creds) stub = rpcstub.LightningStub(channel) request = lnrpc.PayReqString( pay_req=lnInvoiceString, ) response = stub.DecodePayReq(request, metadata=[('macaroon', macaroon)]) # validate results if response.num_msat <= 0: raise BlitzError("zero invoice not allowed", {'invoice': lnInvoiceString}) except Exception as e: raise BlitzError("failed LND invoice decoding", {'invoice': lnInvoiceString}, e) return response
def create_ssh_dialog(): # check parameters try: if len(sys.argv) <= 4: raise BlitzError("incorrect parameters") except Exception as e: handleException(e) servicename = sys.argv[2] toraddress = sys.argv[3] port = sys.argv[4] menuMakeSubscription(servicename, toraddress, port) sys.exit()
def domain_by_ip(): # check parameters try: if len(sys.argv) <= 2: raise BlitzError("incorrect parameters", "") except Exception as e: handleException(e) ip = sys.argv[2] try: domain = get_domain_by_ip(ip) print("domain='{0}'".format(domain)) except Exception as e: handleException(e)
def shop_list(): # check parameters try: if len(sys.argv) <= 2: raise BlitzError("incorrect parameters") except Exception as e: handleException(e) shopurl = sys.argv[2] try: # get data hosts = shopList(shopurl) # output is json list of hosts print(json.dumps(hosts, indent=2)) except Exception as e: handleException(e) sys.exit(0)
def subscription_detail(): # check parameters try: if len(sys.argv) <= 2: raise BlitzError("incorrect parameters", "") except Exception as e: handleException(e) subscription_id = sys.argv[2] httpsTestport = "" if len(sys.argv) > 3: httpsTestport = sys.argv[3] try: sub = get_subscription(subscription_id) # use unix 'getent' to resolve DNS to IP dns_result = subprocess.Popen(["getent", "hosts", subscription_id], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf8') out, err = dns_result.communicate() sub['dns_response'] = "unknown" if subscription_id in out: sub['dns_response'] = out.split(" ")[0] if sub['dns_response'] != sub['ip'] and len(sub['warning']) == 0: sub['warning'] = "Domain resolves not to target IP yet." # when https testport is set - check if you we get a https response sub['https_response'] = -1 if len(httpsTestport) > 0: url = "https://{0}:{1}".format(subscription_id, httpsTestport) try: response = session.get(url) sub['https_response'] = response.status_code except Exception as e: sub['https_response'] = 0 if sub['https_response'] != 200 and len(sub['warning']) == 0: sub['warning'] = "Not able to get HTTPS response." print(json.dumps(sub, indent=2)) except Exception as e: handleException(e)
def shopOrder(shopUrl, hostid, servicename, torTarget, duration, msatsFirst, msatsNext, description=""): print("#### Placeing order ...") shopUrl = normalizeShopUrl(shopUrl) orderid = apiPlaceOrderNew(session, shopUrl, hostid, torTarget) print("#### Waiting until invoice is available ...") loopCount = 0 while True: time.sleep(2) loopCount += 1 print("# Loop {0}".format(loopCount)) order = apiGetOrder(session, shopUrl, orderid) if order['status'] == "R": raise BlitzError("Subscription Rejected", order) if len(order['ln_invoices']) > 0 and order['ln_invoices'][0]['payment_request'] is not None: break if loopCount > 60: raise BlitzError("timeout on getting invoice", order) # get data from now complete order paymentRequestStr = order['ln_invoices'][0]['payment_request'] bridge_id = order['item_details'][0]['product']['id'] bridge_ip = order['item_details'][0]['product']['host']['ip'] bridge_port = order['item_details'][0]['product']['port'] print("#### Decoding invoice and checking ..)") print("# invoice: {0}".format(paymentRequestStr)) paymentRequestDecoded = lndDecodeInvoice(paymentRequestStr) if paymentRequestDecoded is None: sys.exit() print("# amount as advertised: {0} milliSats".format(msatsFirst)) print("# amount in invoice is: {0} milliSats".format(paymentRequestDecoded.num_msat)) if int(msatsFirst) < int(paymentRequestDecoded.num_msat): raise BlitzError("invoice bigger amount than advertised", "advertised({0}) invoice({1})".format(msatsFirst, paymentRequestDecoded.num_msat)) print("#### Paying invoice ...") payedInvoice = lndPayInvoice(paymentRequestStr) print('# OK PAYMENT SENT') print("#### Waiting until bridge is ready ...") loopCount = 0 while True: time.sleep(3) loopCount += 1 print("## Loop {0}".format(loopCount)) bridge = apiGetBridgeStatus(session, shopUrl, bridge_id) if bridge['status'] == "A": break if bridge['status'] == "R": break if loopCount > 120: raise BlitzError("timeout bridge not getting ready", bridge) print("#### Check if port is valid ...") try: bridge_port = int(bridge['port']) except KeyError: raise BlitzError("invalid port (key not found)", bridge) except ValueError: raise BlitzError("invalid port (not a number)", bridge) if bridge_port < 1 or bridge_port > 65535: raise BlitzError("invalid port (not a valid tcp port)", bridge) print("#### Check if duration delivered is as advertised ...") contract_breached = False warning_text = "" secondsDelivered = secondsLeft(parseDate(bridge['suspend_after'])) print("# delivered({0}) promised({1})".format(secondsDelivered, duration)) if (secondsDelivered + 600) < int(duration): contract_breached = True warning_text = "delivered duration shorter than advertised" if bridge['status'] == "R": contract_breached = True try: warningTXT = "rejected: {0}".format(bridge['message']) except Exception as e: warningTXT = "rejected: n/a" # create subscription data for storage subscription = dict() subscription['type'] = "ip2tor-v1" subscription['id'] = bridge['id'] subscription['name'] = servicename subscription['shop'] = shopUrl subscription['active'] = not contract_breached subscription['ip'] = bridge_ip subscription['port'] = bridge_port subscription['duration'] = int(duration) subscription['price_initial'] = int(msatsFirst) subscription['price_extension'] = int(msatsNext) subscription['price_total'] = int(paymentRequestDecoded.num_msat) subscription['time_created'] = str(datetime.now().strftime("%Y-%m-%d %H:%M")) subscription['time_lastupdate'] = str(datetime.now().strftime("%Y-%m-%d %H:%M")) subscription['suspend_after'] = bridge['suspend_after'] subscription['description'] = str(description) subscription['contract_breached'] = contract_breached subscription['warning'] = warning_text subscription['tor'] = torTarget # load, add and store subscriptions try: os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE)) if Path(SUBSCRIPTIONS_FILE).is_file(): print("# load toml file") subscriptions = toml.load(SUBSCRIPTIONS_FILE) else: print("# new toml file") subscriptions = {} if "subscriptions_ip2tor" not in subscriptions: subscriptions['subscriptions_ip2tor'] = [] subscriptions['subscriptions_ip2tor'].append(subscription) subscriptions['shop_ip2tor'] = shopUrl with open(SUBSCRIPTIONS_FILE, 'w') as writer: writer.write(toml.dumps(subscriptions)) writer.close() except Exception as e: eprint(e) raise BlitzError("fail on subscription storage", subscription, e) print("# OK - BRIDGE READY: {0}:{1} -> {2}".format(bridge_ip, bridge_port, torTarget)) return subscription
def dynu_update(domain, token, ip): print("# dynu update IP API call for {0}".format(domain)) # split token to oAuth username and password try: print("Splitting oAuth user & pass:"******":")[0] password = token.split(":")[1] print(username) print(password) except Exception as e: raise BlitzError("failed to split token", token, e) # get API token from oAuth data url="https://api.dynu.com/v2/oauth2/token" headers = {'accept': 'application/json'} print("# calling URL: {0}".format(url)) print("# headers: {0}".format(headers)) try: response = session.get(url, headers=headers, auth=(username, password)) if response.status_code != 200: raise BlitzError("failed HTTP request", url + str(response.status_code)) print("# response-code: {0}".format(response.status_code)) except Exception as e: raise BlitzError("failed HTTP request", url, e) # parse data apitoken="" try: print(response.content) data = json.loads(response.content) apitoken = data["access_token"]; except Exception as e: raise BlitzError("failed parsing data", response.content, e) if len(apitoken) == 0: raise BlitzError("access_token not found", response.content) # get id for domain url = "https://api.dynu.com/v2/dns" headers = {'accept': 'application/json', 'Authorization': "Bearer {0}".format(apitoken)} print("# calling URL: {0}".format(url)) print("# headers: {0}".format(headers)) try: response = session.get(url, headers=headers) if response.status_code != 200: raise BlitzError("failed HTTP request", url + str(response.status_code)) print("# response-code: {0}".format(response.status_code)) except Exception as e: print(e) time.sleep(4) raise BlitzError("failed HTTP request", url, e) # parse data id_for_domain=0 try: print(response.content) data = json.loads(response.content) for entry in data["domains"]: if entry['name'] == domain: id_for_domain = entry['id'] break except Exception as e: print(e) time.sleep(4) raise BlitzError("failed parsing data", response.content, e) if id_for_domain == 0: raise BlitzError("domain not found", response.content) # update ip address url = "https://api.dynu.com/v2/dns/{0}".format(id_for_domain) print("# calling URL: {0}".format(url)) headers = {'accept': 'application/json', 'Authorization': "Bearer {0}".format(apitoken)} print("# headers: {0}".format(headers)) data = { "name": domain, "ipv4Address": ip, "ipv4": True, "ipv6": False } data = json.dumps(data) print("# post data: {0}".format(data)) try: response = session.post(url, headers=headers, data=data) if response.status_code != 200: raise BlitzError("failed HTTP request", url + str(response.status_code)) print("# response-code: {0}".format(response.status_code)) except Exception as e: print(e) time.sleep(4) raise BlitzError("failed HTTP request", url, e) return response.content
def subscriptionExtend(shopUrl, bridgeid, durationAdvertised, msatsNext, bridge_suspendafter): warningTXT = "" contract_breached = False print("#### Placing extension order ...") shopUrl = normalizeShopUrl(shopUrl) orderid = apiPlaceOrderExtension(session, shopUrl, bridgeid) print("#### Waiting until invoice is available ...") loopCount = 0 while True: time.sleep(2) loopCount += 1 print("## Loop {0}".format(loopCount)) order = apiGetOrder(session, shopUrl, orderid) if len(order['ln_invoices']) > 0 and order['ln_invoices'][0]['payment_request'] is not None: break if loopCount > 120: raise BlitzError("timeout on getting invoice", order) paymentRequestStr = order['ln_invoices'][0]['payment_request'] print("#### Decoding invoice and checking ..)") print("# invoice: {0}".format(paymentRequestStr)) paymentRequestDecoded = lndDecodeInvoice(paymentRequestStr) if paymentRequestDecoded is None: sys.exit() print("# amount as advertised: {0} milliSats".format(msatsNext)) print("# amount in invoice is: {0} milliSats".format(paymentRequestDecoded.num_msat)) if int(msatsNext) < int(paymentRequestDecoded.num_msat): raise BlitzError("invoice bigger amount than advertised", "advertised({0}) invoice({1})".format(msatsNext, paymentRequestDecoded.num_msat)) print("#### Paying invoice ...") payedInvoice = lndPayInvoice(paymentRequestStr) print("#### Check if bridge was extended ...") bridge = None loopCount = 0 while True: time.sleep(3) loopCount += 1 print("## Loop {0}".format(loopCount)) try: bridge = apiGetBridgeStatus(session, shopUrl, bridgeid) if bridge['status'] == "R": contract_breached = True try: warningTXT = "rejected: {0}".format(bridge['message']) except Exception as e: warningTXT = "rejected: n/a" break if bridge['suspend_after'] != bridge_suspendafter: break except Exception as e: eprint(e) print("# EXCEPTION on apiGetBridgeStatus") if loopCount > 240: warningTXT = "timeout on last payed extension" contract_breached = True break if bridge and not contract_breached: print("#### Check if extension duration is as advertised ...") secondsLeftOld = secondsLeft(parseDate(bridge_suspendafter)) secondsLeftNew = secondsLeft(parseDate(bridge['suspend_after'])) secondsExtended = secondsLeftNew - secondsLeftOld print("# secondsExtended({0}) promised({1})".format(secondsExtended, durationAdvertised)) if secondsExtended < int(durationAdvertised): contract_breached = True warningTXT = "delivered duration shorter than advertised" # load, update and store subscriptions try: print("# load toml file") os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE)) subscriptions = toml.load(SUBSCRIPTIONS_FILE) for idx, subscription in enumerate(subscriptions['subscriptions_ip2tor']): if subscription['id'] == bridgeid: subscription['suspend_after'] = str(bridge['suspend_after']) subscription['time_lastupdate'] = str(datetime.now().strftime("%Y-%m-%d %H:%M")) subscription['price_total'] += int(paymentRequestDecoded.num_msat) subscription['contract_breached'] = contract_breached subscription['warning'] = warningTXT if contract_breached: subscription['active'] = False with open(SUBSCRIPTIONS_FILE, 'w') as writer: writer.write(toml.dumps(subscriptions)) writer.close() break except Exception as e: eprint(e) raise BlitzError("fail on subscription storage", org=e) print("# BRIDGE GOT EXTENDED: {0} -> {1}".format(bridge_suspendafter, bridge['suspend_after']))
def subscriptions_new(ip, dnsservice, domain, token, target): # domain needs to be the full domain name if domain.find(".") == -1: raise BlitzError("not a fully qualified domain name", domain) # check if domain already exists if len(get_subscription(domain)) > 0: raise BlitzError("domain already exists", domain) # make sure lets encrypt client is installed os.system("/home/admin/config.scripts/bonus.letsencrypt.sh on") # dyndns real_ip = ip if ip == "dyndns": update_url = "" if dnsservice == "duckdns": update_url = "https://www.duckdns.org/update?domains={0}&token={1}".format(domain, token, ip) subprocess.run(['/home/admin/config.scripts/internet.dyndomain.sh', 'on', domain, update_url], stdout=subprocess.PIPE).stdout.decode('utf-8').strip() real_ip = cfg.public_ip if dnsservice == "dynu": raise BlitzError("not implemented", "dynamic ip updating for dynu.com not implemented yet ", e) sys.exit(0) # update DNS with actual IP if dnsservice == "duckdns": print("# dnsservice=duckdns --> update {0}".format(domain)) duckdns_update(domain, token, real_ip) if dnsservice == "dynu": print("# dnsservice=dynu --> update {0}".format(domain)) dynu_update(domain, token, real_ip) # create subscription data for storage subscription = dict() subscription['type'] = "letsencrypt-v1" subscription['id'] = domain subscription['active'] = True subscription['name'] = "{0} for {1}".format(dnsservice, domain) subscription['dnsservice_type'] = dnsservice subscription['dnsservice_token'] = token subscription['ip'] = ip subscription['target'] = target subscription['description'] = "For {0}".format(target) subscription['time_created'] = str(datetime.now().strftime("%Y-%m-%d %H:%M")) subscription['warning'] = "" # load, add and store subscriptions try: os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE)) if Path(SUBSCRIPTIONS_FILE).is_file(): print("# load toml file") subscriptions = toml.load(SUBSCRIPTIONS_FILE) else: print("# new toml file") subscriptions = {} if "subscriptions_letsencrypt" not in subscriptions: subscriptions['subscriptions_letsencrypt'] = [] subscriptions['subscriptions_letsencrypt'].append(subscription) with open(SUBSCRIPTIONS_FILE, 'w') as writer: writer.write(toml.dumps(subscriptions)) writer.close() except Exception as e: eprint(e) raise BlitzError("fail on subscription storage", str(subscription), e) # run the ACME script print("# Running letsencrypt ACME script ...") acme_result = subprocess.Popen( ["/home/admin/config.scripts/bonus.letsencrypt.sh", "issue-cert", dnsservice, domain, token, target], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf8') out, err = acme_result.communicate() eprint(str(out)) eprint(str(err)) if out.find("error=") > -1: time.sleep(6) raise BlitzError("letsancrypt acme failed", out) print("# OK - LETSENCRYPT DOMAIN IS READY") return subscription