Merge pull request #1353 from frennkie/code-style

ip2tor: code style
This commit is contained in:
frennkie
2020-07-17 20:54:23 +02:00
committed by GitHub
3 changed files with 607 additions and 481 deletions

View File

@@ -1,28 +1,32 @@
#!/usr/bin/python3
import sys
import locale
import requests
import codecs
import json
import math
import os
import sys
import time
import datetime, time
import codecs, grpc, os
from datetime import datetime
from pathlib import Path
import grpc
import requests
import toml
from blitzpy import RaspiBlitzConfig
from lndlibs import rpc_pb2 as lnrpc
from lndlibs import rpc_pb2_grpc as rpcstub
####### SCRIPT INFO #########
#####################
# SCRIPT INFO
#####################
# display config script info
if len(sys.argv) <= 1 or sys.argv[1] == "-h" or sys.argv[1] == "help":
print("# manage ip2tor subscriptions for raspiblitz")
print("# blitz.subscriptions.ip2tor.py create-ssh-dialog servicename toraddress torport")
print("# blitz.subscriptions.ip2tor.py shop-list shopurl")
print("# blitz.subscriptions.ip2tor.py shop-order shopurl servicename hostid toraddress:port duration msatsFirst msatsNext [description]")
print("# blitz.subscriptions.ip2tor.py shop-order shopurl servicename hostid toraddress:port duration "
"msatsFirst msatsNext [description]")
print("# blitz.subscriptions.ip2tor.py subscriptions-list")
print("# blitz.subscriptions.ip2tor.py subscriptions-renew secondsBeforeSuspend")
print("# blitz.subscriptions.ip2tor.py subscription-cancel id")
@@ -30,19 +34,25 @@ if len(sys.argv) <= 1 or sys.argv[1] == "-h" or sys.argv[1] == "help":
print("# blitz.subscriptions.ip2tor.py ip-by-tor onionaddress")
sys.exit(1)
####### BASIC SETTINGS #########
#####################
# BASIC SETTINGS
#####################
session = requests.session()
if Path("/mnt/hdd/raspiblitz.conf").is_file():
cfg = RaspiBlitzConfig()
cfg.reload()
if cfg.chain.value == "test":
is_testnet = True
else:
is_testnet = False
ENV = "PROD"
# DEFAULT_SHOPURL="shopdeu2vdhazvmllyfagdcvlpflzdyt5gwftmn4hjj3zw2oyelksaid.onion"
DEFAULT_SHOPURL = "ip2tor.fulmo.org"
LND_IP = "127.0.0.1"
LND_ADMIN_MACAROON_PATH="/mnt/hdd/app-data/lnd/data/chain/{0}/{1}net/admin.macaroon".format(cfg.network.value,cfg.chain.value)
LND_ADMIN_MACAROON_PATH = "/mnt/hdd/app-data/lnd/data/chain/{0}/{1}net/admin.macaroon".format(cfg.network.value,
cfg.chain.value)
LND_TLS_PATH = "/mnt/hdd/app-data/lnd/tls.cert"
# make sure to make requests thru TOR 127.0.0.1:9050
session.proxies = {'http': 'socks5h://127.0.0.1:9050', 'https': 'socks5h://127.0.0.1:9050'}
@@ -56,12 +66,12 @@ else:
LND_TLS_PATH = "/Users/rotzoll/Downloads/RaspiBlitzCredentials/tls.cert"
SUBSCRIPTIONS_FILE = "/Users/rotzoll/Downloads/RaspiBlitzCredentials/subscriptions.toml"
if cfg.chain.value == "test":
is_testnet = True
else:
is_testnet = False
####### HELPER CLASSES #########
#####################
# HELPER CLASSES
#####################
class BlitzError(Exception):
def __init__(self, errorShort, errorLong="", errorException=None):
@@ -69,11 +79,15 @@ class BlitzError(Exception):
self.errorLong = str(errorLong)
self.errorException = errorException
####### HELPER FUNCTIONS #########
#####################
# HELPER FUNCTIONS
#####################
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
def handleException(e):
if isinstance(e, BlitzError):
eprint(e.errorLong)
@@ -84,18 +98,21 @@ def handleException(e):
print("error='{0}'".format(str(e)))
sys.exit(1)
def parseDate(datestr):
return datetime.datetime.strptime(datestr,"%Y-%m-%dT%H:%M:%SZ")
return datetime.strptime(datestr, "%Y-%m-%dT%H:%M:%SZ")
def secondsLeft(dateObj):
return round((dateObj - datetime.datetime.utcnow()).total_seconds())
return round((dateObj - datetime.utcnow()).total_seconds())
# takes a shopurl from user input and turns it into the format needed
# also makes sure that .onion addresses run just with http not https
def normalizeShopUrl(shopurlUserInput):
# basic checks and formats
if len(shopurlUserInput) < 3: return ""
if len(shopurlUserInput) < 3:
return ""
shopurlUserInput = shopurlUserInput.lower()
shopurlUserInput = shopurlUserInput.replace(" ", "")
shopurlUserInput = shopurlUserInput.replace("\n", "")
@@ -110,7 +127,7 @@ def normalizeShopUrl(shopurlUserInput):
shopurlUserInput = shopurlUserInput[:shopurlUserInput.find("/")]
# add correct protocol again
if ( not shopurlUserInput.startswith("http://") and not shopurlUserInput.startswith("https://") ):
if not shopurlUserInput.startswith("http://") and not shopurlUserInput.startswith("https://"):
if shopurlUserInput.endswith(".onion"):
shopurlUserInput = "http://{0}".format(shopurlUserInput)
else:
@@ -118,16 +135,18 @@ def normalizeShopUrl(shopurlUserInput):
return shopurlUserInput
####### IP2TOR API CALLS #########
#####################
# IP2TOR API CALLS
#####################
def apiGetHosts(session, shopurl):
print("# apiGetHosts")
hosts = []
# make HTTP request
try:
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, e)
@@ -144,21 +163,27 @@ def apiGetHosts(session, shopurl):
for idx, hostEntry in enumerate(jData):
try:
# ignore if not offering tor bridge
if not hostEntry['offers_tor_bridges']: continue
if not hostEntry['offers_tor_bridges']:
continue
# ignore if duration is less than an hour
if hostEntry['tor_bridge_duration'] < 3600: continue
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
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
if len(hostEntry['name']) < 3:
continue
# ignore id with zero value
if len(hostEntry['id']) < 1: continue
if len(hostEntry['id']) < 1:
continue
# shorten names to 20 chars max
hostEntry['name'] = hostEntry['name'][:20]
except Exception as e:
@@ -169,11 +194,10 @@ def apiGetHosts(session, shopurl):
print("# found {0} valid torbridge hosts".format(len(hosts)))
return hosts
def apiPlaceOrderNew(session, shopurl, hostid, toraddressWithPort):
def apiPlaceOrderNew(session, shopurl, hostid, toraddressWithPort):
print("# apiPlaceOrderNew")
try:
url = "{0}/api/v1/public/order/".format(shopurl)
postData = {
'product': "tor_bridge",
@@ -183,6 +207,7 @@ def apiPlaceOrderNew(session, shopurl, hostid, toraddressWithPort):
'target': toraddressWithPort,
'public_key': ''
}
try:
response = session.post(url, data=postData)
except Exception as e:
raise BlitzError("failed HTTP request", url, e)
@@ -202,12 +227,12 @@ def apiPlaceOrderNew(session, shopurl, hostid, toraddressWithPort):
return jData['id']
def apiPlaceOrderExtension(session, shopurl, bridgeid):
def apiPlaceOrderExtension(session, shopurl, bridgeid):
print("# apiPlaceOrderExtension")
try:
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, e)
@@ -230,12 +255,11 @@ def apiPlaceOrderExtension(session, shopurl, bridgeid):
def apiGetOrder(session, shopurl, orderid):
print("# apiGetOrder")
# make HTTP request
try:
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, e)
@@ -254,13 +278,13 @@ def apiGetOrder(session, shopurl, orderid):
return jData
def apiGetBridgeStatus(session, shopurl, bridgeid):
def apiGetBridgeStatus(session, shopurl, bridgeid):
print("# apiGetBridgeStatus")
# make HTTP request
try:
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, e)
@@ -276,7 +300,10 @@ def apiGetBridgeStatus(session, shopurl, bridgeid):
return jData
####### LND API CALLS #########
#####################
# LND API CALLS
#####################
def lndDecodeInvoice(lnInvoiceString):
try:
@@ -301,6 +328,7 @@ def lndDecodeInvoice(lnInvoiceString):
return response
def lndPayInvoice(lnInvoiceString):
try:
# call LND GRPC API
@@ -324,16 +352,18 @@ def lndPayInvoice(lnInvoiceString):
return response
####### PROCESS FUNCTIONS #########
#####################
# PROCESS FUNCTIONS
#####################
def shopList(shopUrl):
print("#### Getting available options from shop ...")
shopUrl = normalizeShopUrl(shopUrl)
return apiGetHosts(session, shopUrl)
def shopOrder(shopUrl, hostid, servicename, torTarget, duration, msatsFirst, msatsNext, description=""):
def shopOrder(shopUrl, hostid, servicename, torTarget, duration, msatsFirst, msatsNext, description=""):
print("#### Placeing order ...")
shopUrl = normalizeShopUrl(shopUrl)
orderid = apiPlaceOrderNew(session, shopUrl, hostid, torTarget)
@@ -365,7 +395,8 @@ def shopOrder(shopUrl, hostid, servicename, torTarget, duration, msatsFirst, msa
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))
raise BlitzError("invoice bigger amount than advertised",
"advertised({0}) invoice({1})".format(msatsFirst, paymentRequestDecoded.num_msat))
print("#### Paying invoice ...")
payedInvoice = lndPayInvoice(paymentRequestStr)
@@ -397,7 +428,7 @@ def shopOrder(shopUrl, hostid, servicename, torTarget, duration, msatsFirst, msa
warning_text = "delivered duration shorter than advertised"
# create subscription data for storage
subscription = {}
subscription = dict()
subscription['type'] = "ip2tor-v1"
subscription['id'] = bridge['id']
subscription['name'] = servicename
@@ -409,8 +440,8 @@ def shopOrder(shopUrl, hostid, servicename, torTarget, duration, msatsFirst, msa
subscription['price_initial'] = int(msatsFirst)
subscription['price_extension'] = int(msatsNext)
subscription['price_total'] = int(paymentRequestDecoded.num_msat)
subscription['time_created'] = str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))
subscription['time_lastupdate'] = str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))
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
@@ -436,13 +467,13 @@ def shopOrder(shopUrl, hostid, servicename, torTarget, duration, msatsFirst, msa
except Exception as e:
eprint(e)
raise BlitzError("fail on subscription storage",subscription, e)
raise BlitzError("fail on subscription storage", str(subscription), e)
print("# OK - BRIDGE READY: {0}:{1} -> {2}".format(bridge_ip, subscription['port'], torTarget))
return subscription
def subscriptionExtend(shopUrl, bridgeid, durationAdvertised, msatsNext, bridge_suspendafter):
def subscriptionExtend(shopUrl, bridgeid, durationAdvertised, msatsNext, bridge_suspendafter):
warningTXT = ""
contract_breached = False
@@ -462,7 +493,6 @@ def subscriptionExtend(shopUrl, bridgeid, durationAdvertised, msatsNext, bridge_
if loopCount > 120:
raise BlitzError("timeout on getting invoice", order)
paymentRequestStr = order['ln_invoices'][0]['payment_request']
print("#### Decoding invoice and checking ..)")
@@ -472,12 +502,14 @@ def subscriptionExtend(shopUrl, bridgeid, durationAdvertised, msatsNext, bridge_
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))
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)
@@ -495,7 +527,7 @@ def subscriptionExtend(shopUrl, bridgeid, durationAdvertised, msatsNext, bridge_
contract_breached = True
break
if not contract_breached:
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']))
@@ -513,7 +545,7 @@ def subscriptionExtend(shopUrl, bridgeid, durationAdvertised, msatsNext, bridge_
for idx, subscription in enumerate(subscriptions['subscriptions_ip2tor']):
if subscription['id'] == bridgeid:
subscription['suspend_after'] = str(bridge['suspend_after'])
subscription['time_lastupdate'] = str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))
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
@@ -526,11 +558,14 @@ def subscriptionExtend(shopUrl, bridgeid, durationAdvertised, msatsNext, bridge_
except Exception as e:
eprint(e)
raise BlitzError("fail on subscription storage",subscription, e)
raise BlitzError("fail on subscription storage", "", e)
print("# BRIDGE GOT EXTENDED: {0} -> {1}".format(bridge_suspendafter, bridge['suspend_after']))
def menuMakeSubscription(blitzServiceName, torAddress, torPort):
# late imports - so that rest of script can run also if dependency is not available
from dialog import Dialog
torTarget = "{0}:{1}".format(torAddress, torPort)
@@ -557,7 +592,8 @@ def menuMakeSubscription(blitzServiceName, torAddress, torPort):
title="Shop Address")
# if user canceled
if code != d.OK: sys.exit(0)
if code != d.OK:
sys.exit(0)
# get host list from shop
shopurl = text
@@ -578,7 +614,8 @@ The shop has no available offers at the moment.
Try again later, enter another address or cancel.
''', title="ERROR")
# ok we got hosts - continue
else: break
else:
break
###############################
# PHASE 2: SELECT SUBSCRIPTION
@@ -587,7 +624,15 @@ Try again later, enter another address or cancel.
host = None
choices = []
for idx, hostEntry in enumerate(hosts):
choices.append( ("{0}".format(idx), "{0} ({1} hours, first: {2} sats, next: {3} sats)".format(hostEntry['name'].ljust(20), hostEntry['tor_bridge_duration_hours'], hostEntry['tor_bridge_price_initial_sats'], hostEntry['tor_bridge_price_extension_sats'])) )
choices.append(
("{0}".format(idx),
"{0} ({1} hours, first: {2} sats, next: {3} sats)".format(
hostEntry['name'].ljust(20),
hostEntry['tor_bridge_duration_hours'],
hostEntry['tor_bridge_price_initial_sats'],
hostEntry['tor_bridge_price_extension_sats'])
)
)
while True:
@@ -643,7 +688,8 @@ More information on the service you can find under:
blitzServiceName
)
code = d.msgbox(text, title=host['name'], ok_label="Back", extra_button=True, extra_label="AGREE" ,width=75, height=30)
code = d.msgbox(text, title=host['name'], ok_label="Back", extra_button=True, extra_label="AGREE", width=75,
height=30)
# if user AGREED break loop and continue with selected host
if code == "extra": break
@@ -656,7 +702,8 @@ More information on the service you can find under:
try:
os.system('clear')
subscription = shopOrder(shopurl, host['id'], blitzServiceName, torTarget, host['tor_bridge_duration'], host['tor_bridge_price_initial'],host['tor_bridge_price_extension'],description)
subscription = shopOrder(shopurl, host['id'], blitzServiceName, torTarget, host['tor_bridge_duration'],
host['tor_bridge_price_initial'], host['tor_bridge_price_extension'], description)
except BlitzError as be:
@@ -667,7 +714,7 @@ More information on the service you can find under:
be.errorShort == "invalid port" or
be.errorShort == "timeout bridge not getting ready"):
# error happend after payment
# error happened after payment
exitcode = Dialog(dialog="dialog", autowidgetsize=True).msgbox('''
You DID PAY the initial fee.
But the service was not able to provide service.
@@ -676,7 +723,7 @@ Error: {0}
'''.format(be.errorShort), title="Error on Subscription", extra_button=True, extra_label="Details")
else:
# error happend before payment
# error happened before payment
exitcode = Dialog(dialog="dialog", autowidgetsize=True).msgbox('''
You DID NOT PAY the initial fee.
The service was not able to provide service.
@@ -704,7 +751,7 @@ Error: {0}
except Exception as e:
# unkown error happend
# unknown error happened
os.system('clear')
print('###### EXCEPTION DETAIL FOR DEBUG #######')
print("")
@@ -718,7 +765,7 @@ Error: {0}
input("Press Enter to continue ...")
sys.exit(1)
# if LND REST or LND GRPS service ... add bridge IP to TLS
# if LND REST or LND GRPC service ... add bridge IP to TLS
if servicename == "LND-REST-API" or servicename == "LND-GRPC-API":
os.system("sudo /home/admin/config.scripts/lnd.tlscert.sh ip-add {0}".format(subscription['ip']))
os.system("sudo /home/admin/config.scripts/lnd.credentials.sh reset tls")
@@ -762,7 +809,9 @@ MAIN MENU > Manage Subscriptions > My Subscriptions
)
####### COMMANDS #########
#####################
# COMMANDS
#####################
###############
# CREATE SSH DIALOG
@@ -773,15 +822,15 @@ if sys.argv[1] == "create-ssh-dialog":
# check parameters
try:
if len(sys.argv) <= 4: raise BlitzError("incorrect parameters","")
servicename = sys.argv[2]
toraddress = sys.argv[3]
port = sys.argv[4]
if len(sys.argv) <= 4:
raise BlitzError("incorrect parameters", "")
except Exception as e:
handleException(e)
# late imports - so that rest of script can run also if dependency is not available
from dialog import Dialog
servicename = sys.argv[2]
toraddress = sys.argv[3]
port = sys.argv[4]
menuMakeSubscription(servicename, toraddress, port)
sys.exit()
@@ -795,31 +844,37 @@ if sys.argv[1] == "shop-list":
# check parameters
try:
if len(sys.argv) <= 2: raise BlitzError("incorrect parameters","")
if len(sys.argv) <= 2:
raise BlitzError("incorrect parameters", "")
except Exception as e:
handleException(e)
shopurl = sys.argv[2]
except Exception as e:
handleException(e)
# get data
try:
# get data
hosts = shopList(shopurl)
except Exception as e:
handleException(e)
# output is json list of hosts
print(json.dumps(hosts, indent=2))
except Exception as e:
handleException(e)
sys.exit(0)
###############
##########################
# SHOP ORDER
# call from web interface
###############
##########################
if sys.argv[1] == "shop-order":
# check parameters
try:
if len(sys.argv) <= 8: raise BlitzError("incorrect parameters","")
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]
@@ -831,22 +886,19 @@ if sys.argv[1] == "shop-order":
description = sys.argv[9]
else:
description = ""
except Exception as e:
handleException(e)
# get data
try:
subscription = shopOrder(shopurl, hostid, servicename, toraddress, duration, msatsFirst, msatsNext, description)
except Exception as e:
handleException(e)
# output json ordered bridge
print(json.dumps(subscription, indent=2))
sys.exit()
except Exception as e:
handleException(e)
#######################
# SUBSCRIPTIONS LIST
# call in intervalls from background process
# call in intervals from background process
#######################
if sys.argv[1] == "subscriptions-list":
@@ -869,7 +921,7 @@ if sys.argv[1] == "subscriptions-list":
#######################
# SUBSCRIPTIONS RENEW
# call in intervalls from background process
# call in intervals from background process
#######################
if sys.argv[1] == "subscriptions-renew":
@@ -879,12 +931,13 @@ if sys.argv[1] == "subscriptions-renew":
# check parameters
try:
secondsBeforeSuspend = int(sys.argv[2])
if secondsBeforeSuspend < 0: secondsBeforeSuspend = 0
if secondsBeforeSuspend < 0:
secondsBeforeSuspend = 0
except Exception as e:
print("# running with secondsBeforeSuspend=0")
secondsBeforeSuspend = 0
# check if any active subscrpitions are below the secondsBeforeSuspend - if yes extend
# check if any active subscriptions are below the secondsBeforeSuspend - if yes extend
try:
@@ -901,7 +954,8 @@ if sys.argv[1] == "subscriptions-renew":
if subscription['active'] and subscription['type'] == "ip2tor-v1":
secondsToRun = secondsLeft(parseDate(subscription['suspend_after']))
if secondsToRun < secondsBeforeSuspend:
print("# RENEW: subscription {0} with {1} seconds to run".format(subscription['id'],secondsToRun))
print("# RENEW: subscription {0} with {1} seconds to run".format(subscription['id'],
secondsToRun))
subscriptionExtend(
subscription['shop'],
subscription['id'],
@@ -910,12 +964,13 @@ if sys.argv[1] == "subscriptions-renew":
subscription['suspend_after']
)
else:
print("# GOOD: subscription {0} with {1} seconds to run".format(subscription['id'],secondsToRun))
print("# GOOD: subscription {0} with {1} "
"seconds to run".format(subscription['id'], secondsToRun))
except BlitzError as be:
# write error into subscription warning
subs = toml.load(SUBSCRIPTIONS_FILE)
for idx, sub in enumerate(subs['subscriptions_ip2tor']):
for sub in subs['subscriptions_ip2tor']:
if sub['id'] == subscription['id']:
sub['warning'] = "Exception on Renew: {0}".format(be.errorShort)
if be.errorShort == "invoice bigger amount than advertised":
@@ -947,13 +1002,14 @@ if sys.argv[1] == "subscription-cancel":
# check parameters
try:
if len(sys.argv) <= 2: raise BlitzError("incorrect parameters","")
subscriptionID = sys.argv[2]
if len(sys.argv) <= 2:
raise BlitzError("incorrect parameters", "")
except Exception as e:
handleException(e)
try:
subscriptionID = sys.argv[2]
try:
os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE))
subs = toml.load(SUBSCRIPTIONS_FILE)
newList = []
@@ -983,11 +1039,13 @@ if sys.argv[1] == "subscription-by-service":
# check parameters
try:
if len(sys.argv) <= 2: raise BlitzError("incorrect parameters","")
servicename = sys.argv[2]
if len(sys.argv) <= 2:
raise BlitzError("incorrect parameters", "")
except Exception as e:
handleException(e)
servicename = sys.argv[2]
try:
if os.path.isfile(SUBSCRIPTIONS_FILE):
os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE))
@@ -1017,11 +1075,13 @@ if sys.argv[1] == "ip-by-tor":
# check parameters
try:
if len(sys.argv) <= 2: raise BlitzError("incorrect parameters","")
onion = sys.argv[2]
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):
os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE))
@@ -1043,5 +1103,5 @@ if sys.argv[1] == "ip-by-tor":
sys.exit(1)
# unkown command
print("# unkown command")
# unknown command
print("# unknown command")

View File

@@ -1,19 +1,20 @@
#!/usr/bin/python3
import sys
import locale
import requests
import json
import math
import time
import datetime, time
import os
import subprocess
import codecs, grpc, os
import sys
import time
from datetime import datetime
from pathlib import Path
import requests
import toml
from blitzpy import RaspiBlitzConfig
####### SCRIPT INFO #########
#####################
# SCRIPT INFO
#####################
# - this subscription does not require any payments
# - the recurring part is managed by the lets encrypt ACME script
@@ -29,7 +30,9 @@ if len(sys.argv) <= 1 or sys.argv[1] == "-h" or sys.argv[1] == "help":
print("# blitz.subscriptions.ip2tor.py subscription-cancel <id>")
sys.exit(1)
####### BASIC SETTINGS #########
#####################
# BASIC SETTINGS
#####################
SUBSCRIPTIONS_FILE = "/mnt/hdd/app-data/subscriptions/subscriptions.toml"
@@ -41,7 +44,10 @@ session = requests.session()
if cfg.run_behind_tor:
session.proxies = {'http': 'socks5h://127.0.0.1:9050', 'https': 'socks5h://127.0.0.1:9050'}
####### HELPER CLASSES #########
#####################
# HELPER CLASSES
#####################
class BlitzError(Exception):
def __init__(self, errorShort, errorLong="", errorException=None):
@@ -49,11 +55,15 @@ class BlitzError(Exception):
self.errorLong = str(errorLong)
self.errorException = errorException
####### HELPER FUNCTIONS #########
#####################
# HELPER FUNCTIONS
#####################
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
def handleException(e):
if isinstance(e, BlitzError):
eprint(e.errorLong)
@@ -64,30 +74,35 @@ def handleException(e):
print("error='{0}'".format(str(e)))
sys.exit(1)
def getsubdomain(fulldomainstring):
return fulldomainstring.split('.')[0]
####### API Calls to DNS Servcies #########
############################
# API Calls to DNS Servcies
############################
def duckDNSupdate(domain, token, ip):
print("# duckDNS update IP API call for {0}".format(domain))
# make HTTP request
try:
url = "https://www.duckdns.org/update?domains={0}&token={1}&ip={2}".format(getsubdomain(domain), token, ip)
try:
response = session.get(url)
if response.status_code != 200:
raise BlitzError("failed HTTP code", str(response.status_code))
except Exception as e:
raise BlitzError("failed HTTP request", url, e)
if response.status_code != 200:
raise BlitzError("failed HTTP code",response.status_code)
return response.content
####### PROCESS FUNCTIONS #########
#####################
# PROCESS FUNCTIONS
#####################
def subscriptionsNew(ip, dnsservice, id, token, target):
# id needs to the full domain name
if id.find(".") == -1:
raise BlitzError("not a fully qualified domainname", dnsservice_id)
@@ -104,8 +119,9 @@ def subscriptionsNew(ip, dnsservice, id, token, target):
if ip == "dyndns":
updateURL = ""
if dnsservice == "duckdns":
updateURL=="https://www.duckdns.org/update?domains={0}&token={1}".format(getsubdomain(domain), token, ip)
subprocess.run(['/home/admin/config.scriprs/internet.dyndomain.sh', 'on', id, updateURL], stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
updateURL = "https://www.duckdns.org/update?domains={0}&token={1}".format(getsubdomain(domain), token, ip)
subprocess.run(['/home/admin/config.scriprs/internet.dyndomain.sh', 'on', id, updateURL],
stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
realip = cfg.public_ip
# update DNS with actual IP
@@ -114,14 +130,16 @@ def subscriptionsNew(ip, dnsservice, id, token, target):
# run the ACME script
print("# Running letsencrypt ACME script ...")
acmeResult=subprocess.Popen(["/home/admin/config.scripts/bonus.letsencrypt.sh", "issue-cert", dnsservice, id, token, target], stdout=subprocess.PIPE, stderr = subprocess.STDOUT, encoding='utf8')
acmeResult = subprocess.Popen(
["/home/admin/config.scripts/bonus.letsencrypt.sh", "issue-cert", dnsservice, id, token, target],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf8')
out, err = acmeResult.communicate()
if out.find("error=") > -1:
time.sleep(6)
raise BlitzError("letsancrypt acme failed", out)
# create subscription data for storage
subscription = {}
subscription = dict()
subscription['type'] = "letsencrypt-v1"
subscription['id'] = id
subscription['active'] = True
@@ -131,7 +149,7 @@ def subscriptionsNew(ip, dnsservice, id, token, target):
subscription['ip'] = ip
subscription['target'] = target
subscription['description'] = "For {0}".format(target)
subscription['time_created'] = str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))
subscription['time_created'] = str(datetime.now().strftime("%Y-%m-%d %H:%M"))
subscription['warning'] = ""
# load, add and store subscriptions
@@ -152,17 +170,19 @@ def subscriptionsNew(ip, dnsservice, id, token, target):
except Exception as e:
eprint(e)
raise BlitzError("fail on subscription storage",subscription, e)
raise BlitzError("fail on subscription storage", str(subscription), e)
print("# OK - LETSENCRYPT DOMAIN IS READY")
return subscription
def subscriptionsCancel(id):
# ToDo(frennkie) id is not used..
os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE))
subs = toml.load(SUBSCRIPTIONS_FILE)
newList = []
removedCert = False
removedCert = None
for idx, sub in enumerate(subs['subscriptions_letsencrypt']):
if sub['id'] != subscriptionID:
newList.append(sub)
@@ -172,11 +192,13 @@ def subscriptionsCancel(id):
# run the ACME script to remove cert
if removedCert:
acmeResult=subprocess.Popen(["/home/admin/config.scripts/bonus.letsencrypt.sh", "remove-cert", removedCert['id'], removedCert['target']], stdout=subprocess.PIPE, stderr = subprocess.STDOUT, encoding='utf8')
acmeResult = subprocess.Popen(
["/home/admin/config.scripts/bonus.letsencrypt.sh", "remove-cert", removedCert['id'],
removedCert['target']], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf8')
out, err = acmeResult.communicate()
if out.find("error=") > -1:
time.sleep(6)
raise BlitzError("letsancrypt acme failed", out)
raise BlitzError("letsencrypt acme failed", out)
# persist change
with open(SUBSCRIPTIONS_FILE, 'w') as writer:
@@ -187,8 +209,8 @@ def subscriptionsCancel(id):
# todo: deinstall letsencrypt if this was last subscription
def getSubscription(subscriptionID):
def getSubscription(subscriptionID):
try:
if Path(SUBSCRIPTIONS_FILE).is_file():
@@ -208,7 +230,6 @@ def getSubscription(subscriptionID):
def getDomainByIP(ip):
# does subscriptin file exists
if Path(SUBSCRIPTIONS_FILE).is_file():
os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE))
@@ -231,6 +252,8 @@ def getDomainByIP(ip):
def menuMakeSubscription():
# late imports - so that rest of script can run also if dependency is not available
from dialog import Dialog
# todo ... copy parts of IP2TOR dialogs
@@ -314,7 +337,7 @@ This looks not like a valid token.
# PHASE 3: Choose what kind of IP: dynDNS, IP2TOR, fixedIP
# ask user for which RaspiBlitz service the bridge should be used
choices = []
choices = list()
choices.append(("IP2TOR", "HTTPS for a IP2TOR Bridge"))
choices.append(("DYNDNS", "HTTPS for {0} DynamicIP DNS".format(dnsservice.upper())))
choices.append(("STATIC", "HTTPS for a static IP"))
@@ -332,6 +355,7 @@ This looks not like a valid token.
# default target are the nginx ip ports
target = "ip"
ip = ""
if tag == "IP2TOR":
@@ -398,7 +422,7 @@ This looks not like a valid IP.
''', title="Unvalid Input")
sys.exit(0)
# create the letsenscript subscription
# create the letsencrypt subscription
try:
os.system("clear")
subscription = subscriptionsNew(ip, dnsservice, domain, token, target)
@@ -413,14 +437,17 @@ to reach the service you wanted.
except Exception as e:
# unkown error happend
# unknown error happened
Dialog(dialog="dialog", autowidgetsize=True).msgbox('''
Unkown Error happend - please report to developers:
Unknown Error happened - please report to developers:
{0}
'''.format(str(e)), title="Exception on Subscription")
sys.exit(1)
####### COMMANDS #########
##################
# COMMANDS
##################
###############
# CREATE SSH DIALOG
@@ -428,24 +455,24 @@ Unkown Error happend - please report to developers:
###############
if sys.argv[1] == "create-ssh-dialog":
# late imports - so that rest of script can run also if dependency is not available
from dialog import Dialog
menuMakeSubscription()
sys.exit()
###############
##########################
# SUBSCRIPTIONS NEW
# call from web interface
###############
##########################
if sys.argv[1] == "subscription-new":
# check parameters
try:
if len(sys.argv) <= 5: raise BlitzError("incorrect parameters","")
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]
@@ -454,19 +481,18 @@ if sys.argv[1] == "subscription-new":
target = "ip&tor"
else:
target = sys.argv[6]
except Exception as e:
handleException(e)
# create the subscription
try:
subscription = subscriptionsNew(ip, dnsservice_type, dnsservice_id, dnsservice_token, target)
except Exception as e:
handleException(e)
# output json ordered bridge
print(json.dumps(subscription, indent=2))
sys.exit()
except Exception as e:
handleException(e)
#######################
# SUBSCRIPTIONS LIST
#######################
@@ -496,11 +522,12 @@ if sys.argv[1] == "subscription-detail":
# check parameters
try:
if len(sys.argv) <= 2: raise BlitzError("incorrect parameters","")
subscriptionID = sys.argv[2]
if len(sys.argv) <= 2:
raise BlitzError("incorrect parameters", "")
except Exception as e:
handleException(e)
subscriptionID = sys.argv[2]
try:
sub = getSubscription(subscriptionID)
print(json.dumps(sub, indent=2))
@@ -518,11 +545,13 @@ if sys.argv[1] == "domain-by-ip":
# check parameters
try:
if len(sys.argv) <= 2: raise BlitzError("incorrect parameters","")
ip = sys.argv[2]
if len(sys.argv) <= 2:
raise BlitzError("incorrect parameters", "")
except Exception as e:
handleException(e)
ip = sys.argv[2]
try:
domain = getDomainByIP(ip)
@@ -540,19 +569,18 @@ if sys.argv[1] == "subscription-cancel":
# check parameters
try:
if len(sys.argv) <= 2: raise BlitzError("incorrect parameters","")
subscriptionID = sys.argv[2]
if len(sys.argv) <= 2:
raise BlitzError("incorrect parameters", "")
except Exception as e:
handleException(e)
subscriptionID = sys.argv[2]
try:
subscriptionsCancel(subscriptionID)
except Exception as e:
handleException(e)
sys.exit(0)
# unkown command
print("# unkown command")
# unknown command
print("# unknown command")

View File

@@ -4,16 +4,15 @@
# SSH Dialogs to manage Subscriptions on the RaspiBlitz
########################################################
import sys
import math
import time
import toml
import os
import subprocess
import sys
import time
from datetime import datetime
from dialog import Dialog
import toml
from blitzpy import RaspiBlitzConfig
from dialog import Dialog
# constants for standard services
LND_REST_API = "LND-REST-API"
@@ -28,21 +27,28 @@ cfg.reload()
# basic values
SUBSCRIPTIONS_FILE = "/mnt/hdd/app-data/subscriptions/subscriptions.toml"
####### HELPER FUNCTIONS #########
#######################
# HELPER FUNCTIONS
#######################
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
def parseDateIP2TORSERVER(datestr):
return datetime.datetime.strptime(datestr,"%Y-%m-%dT%H:%M:%S.%fZ")
return datetime.strptime(datestr, "%Y-%m-%dT%H:%M:%S.%fZ")
def secondsLeft(dateObj):
return round((dateObj - datetime.datetime.utcnow()).total_seconds())
return round((dateObj - datetime.utcnow()).total_seconds())
####### SSH MENU FUNCTIONS #########
#######################
# SSH MENU FUNCTIONS
#######################
def mySubscriptions():
# check if any subscriptions are available
countSubscriptions = 0
try:
@@ -52,7 +58,8 @@ def mySubscriptions():
countSubscriptions += len(subs['subscriptions_ip2tor'])
if 'subscriptions_letsencrypt' in subs:
countSubscriptions += len(subs['subscriptions_letsencrypt'])
except Exception as e: pass
except Exception as e:
pass
if countSubscriptions == 0:
Dialog(dialog="dialog", autowidgetsize=True).msgbox('''
You have no active or inactive subscriptions.
@@ -101,7 +108,8 @@ You have no active or inactive subscriptions.
choices=choices, cancel_label="Back", width=65, height=15, title="My Subscriptions")
# if user chosses CANCEL
if code != d.OK: return
if code != d.OK:
return
# get data of selected subscrption
selectedSub = lookup[str(tag)]
@@ -165,24 +173,30 @@ The following additional information is available:
description=selectedSub['description'],
service=selectedSub['name']
)
else:
text = "no text?! FIXME"
if selectedSub['active']:
extraLable = "CANCEL SUBSCRIPTION"
else:
extraLable = "DELETE SUBSCRIPTION"
code = d.msgbox(text, title="Subscription Detail", ok_label="Back", extra_button=True, extra_label=extraLable ,width=75, height=30)
code = d.msgbox(text, title="Subscription Detail", ok_label="Back", extra_button=True, extra_label=extraLable,
width=75, height=30)
# user wants to delete this subscription
# call the responsible sub script for deletion just in case any subscription needs to do some extra api calls when canceling
# call the responsible sub script for deletion just in case any subscription needs to do some extra
# api calls when canceling
if code == "extra":
os.system("clear")
if selectedSub['type'] == "letsencrypt-v1":
cmd="python /home/admin/config.scripts/blitz.subscriptions.letsencrypt.py subscription-cancel {0}".format(selectedSub['id'])
cmd = "python /home/admin/config.scripts/blitz.subscriptions.letsencrypt.py subscription-cancel {0}".format(
selectedSub['id'])
print("# running: {0}".format(cmd))
os.system(cmd)
time.sleep(2)
elif selectedSub['type'] == "ip2tor-v1":
cmd="python /home/admin/config.scripts/blitz.subscriptions.ip2tor.py subscription-cancel {0}".format(selectedSub['id'])
cmd = "python /home/admin/config.scripts/blitz.subscriptions.ip2tor.py subscription-cancel {0}".format(
selectedSub['id'])
print("# running: {0}".format(cmd))
os.system(cmd)
time.sleep(2)
@@ -193,9 +207,12 @@ The following additional information is available:
# loop until no more subscriptions or user chooses CANCEL on subscription list
mySubscriptions()
####### SSH MENU #########
choices = []
#######################
# SSH MENU
#######################
choices = list()
choices.append(("LIST", "My Subscriptions"))
choices.append(("NEW1", "+ IP2TOR Bridge (paid)"))
choices.append(("NEW2", "+ LetsEncrypt HTTPS Domain (free)"))
@@ -210,16 +227,19 @@ code, tag = d.menu(
if code != d.OK:
sys.exit(0)
####### MANAGE SUBSCRIPTIONS #########
#######################
# MANAGE SUBSCRIPTIONS
#######################
if tag == "LIST":
mySubscriptions()
sys.exit(0)
####### NEW LETSENCRYPT HTTPS DOMAIN #########
###############################
# NEW LETSENCRYPT HTTPS DOMAIN
###############################
if tag == "NEW2":
# run creating a new IP2TOR subscription
os.system("clear")
cmd = "python /home/admin/config.scripts/blitz.subscriptions.letsencrypt.py create-ssh-dialog"
@@ -227,7 +247,10 @@ if tag == "NEW2":
os.system(cmd)
sys.exit(0)
####### NEW IP2TOR BRIDGE #########
###############################
# NEW IP2TOR BRIDGE
###############################
if tag == "NEW1":
@@ -253,22 +276,28 @@ your RaspiBlitz behind TOR.
os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE))
subs = toml.load(SUBSCRIPTIONS_FILE)
for sub in subs['subscriptions_ip2tor']:
if not sub['active']: next
if sub['active'] and sub['name'] == LND_REST_API: lnd_rest_api=True
if sub['active'] and sub['name'] == LND_GRPC_API: lnd_grpc_api=True
if sub['active'] and sub['name'] == LNBITS: lnbits=True
if sub['active'] and sub['name'] == BTCPAY: btcpay=True
if not sub['active']:
continue
if sub['active'] and sub['name'] == LND_REST_API:
lnd_rest_api = True
if sub['active'] and sub['name'] == LND_GRPC_API:
lnd_grpc_api = True
if sub['active'] and sub['name'] == LNBITS:
lnbits = True
if sub['active'] and sub['name'] == BTCPAY:
btcpay = True
except Exception as e:
print(e)
# check if BTCPayserver is installed
btcPayServer = False
statusData= subprocess.run(['/home/admin/config.scripts/bonus.btcpayserver.sh', 'status'], stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
statusData = subprocess.run(['/home/admin/config.scripts/bonus.btcpayserver.sh', 'status'],
stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
if statusData.find("installed=1") > -1:
btcPayServer = True
# ask user for which RaspiBlitz service the bridge should be used
choices = []
choices = list()
choices.append(("REST", "LND REST API {0}".format("--> ALREADY BRIDGED" if lnd_rest_api else "")))
choices.append(("GRPC", "LND gRPC API {0}".format("--> ALREADY BRIDGED" if lnd_grpc_api else "")))
if cfg.lnbits:
@@ -293,22 +322,26 @@ your RaspiBlitz behind TOR.
if tag == "REST":
# get TOR address for REST
servicename = LND_REST_API
torAddress = subprocess.run(['sudo', 'cat', '/mnt/hdd/tor/lndrest8080/hostname'], stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
torAddress = subprocess.run(['sudo', 'cat', '/mnt/hdd/tor/lndrest8080/hostname'],
stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
torPort = 8080
if tag == "GRPC":
# get TOR address for GRPC
servicename = LND_GRPC_API
torAddress = subprocess.run(['sudo', 'cat', '/mnt/hdd/tor/lndrpc10009/hostname'], stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
torAddress = subprocess.run(['sudo', 'cat', '/mnt/hdd/tor/lndrpc10009/hostname'],
stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
torPort = 10009
if tag == "LNBITS":
# get TOR address for LNBits
servicename = LNBITS
torAddress = subprocess.run(['sudo', 'cat', '/mnt/hdd/tor/lnbits/hostname'], stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
torAddress = subprocess.run(['sudo', 'cat', '/mnt/hdd/tor/lnbits/hostname'],
stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
torPort = 443
if tag == "BTCPAY":
# get TOR address for BTCPAY
servicename = BTCPAY
torAddress = subprocess.run(['sudo', 'cat', '/mnt/hdd/tor/btcpay/hostname'], stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
torAddress = subprocess.run(['sudo', 'cat', '/mnt/hdd/tor/btcpay/hostname'],
stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
torPort = 443
if tag == "SELF":
servicename = "CUSTOM"
@@ -320,8 +353,10 @@ your RaspiBlitz behind TOR.
title="IP2TOR Bridge Target")
text = text.strip()
os.system("clear")
if code != d.OK: sys.exit(0)
if len(text) == 0: sys.exit(0)
if code != d.OK:
sys.exit(0)
if len(text) == 0:
sys.exit(0)
if text.find('.onion') < 0 or text.find(' ') > 0:
print("Not a TOR Onion Address")
time.sleep(3)
@@ -334,8 +369,10 @@ your RaspiBlitz behind TOR.
title="IP2TOR Bridge Target")
text = text.strip()
os.system("clear")
if code != d.OK: sys.exit(0)
if len(text) == 0: sys.exit(0)
if code != d.OK:
sys.exit(0)
if len(text) == 0:
sys.exit(0)
torPort = int(text)
except Exception as e:
print(e)
@@ -344,7 +381,8 @@ your RaspiBlitz behind TOR.
# run creating a new IP2TOR subscription
os.system("clear")
cmd="python /home/admin/config.scripts/blitz.subscriptions.ip2tor.py create-ssh-dialog {0} {1} {2}".format(servicename,torAddress,torPort)
cmd = "python /home/admin/config.scripts/blitz.subscriptions.ip2tor.py create-ssh-dialog {0} {1} {2}".format(
servicename, torAddress, torPort)
print("# running: {0}".format(cmd))
os.system(cmd)
sys.exit(0)