raspiblitz/home.admin/config.scripts/blitz.subscriptions.ip2tor.py

1165 lines
40 KiB
Python
Raw Normal View History

2020-05-22 19:24:46 +02:00
#!/usr/bin/python3
2020-07-17 19:16:06 +01:00
import codecs
2020-05-22 20:06:59 +02:00
import json
2020-05-23 02:44:08 +02:00
import math
2020-07-17 19:16:06 +01:00
import os
import sys
2020-05-24 13:11:31 +02:00
import time
2020-07-17 19:16:06 +01:00
from datetime import datetime
2020-05-24 13:11:31 +02:00
from pathlib import Path
2020-07-17 19:16:06 +01:00
import grpc
import requests
2020-05-26 02:32:21 +02:00
import toml
sys.path.append('/home/admin/raspiblitz/home.admin/BlitzPy/blitzpy')
from config import RaspiBlitzConfig
from exceptions import BlitzError
from lndlibs import lightning_pb2 as lnrpc
from lndlibs import lightning_pb2_grpc as rpcstub
2020-05-23 13:49:13 +02:00
2020-07-17 19:16:06 +01:00
#####################
# SCRIPT INFO
#####################
2020-05-25 00:19:32 +02:00
2020-05-22 19:24:46 +02:00
# display config script info
if len(sys.argv) <= 1 or sys.argv[1] == "-h" or sys.argv[1] == "help":
2020-05-23 02:44:08 +02:00
print("# manage ip2tor subscriptions for raspiblitz")
2020-07-12 13:34:05 +02:00
print("# blitz.subscriptions.ip2tor.py create-ssh-dialog servicename toraddress torport")
print("# blitz.subscriptions.ip2tor.py shop-list shopurl")
2020-07-17 19:16:06 +01:00
print("# blitz.subscriptions.ip2tor.py shop-order shopurl servicename hostid toraddress:port duration "
"msatsFirst msatsNext [description]")
2020-05-26 17:47:56 +02:00
print("# blitz.subscriptions.ip2tor.py subscriptions-list")
2020-07-12 13:34:05 +02:00
print("# blitz.subscriptions.ip2tor.py subscriptions-renew secondsBeforeSuspend")
print("# blitz.subscriptions.ip2tor.py subscription-cancel id")
print("# blitz.subscriptions.ip2tor.py subscription-by-service servicename")
2020-07-14 19:31:43 +02:00
print("# blitz.subscriptions.ip2tor.py ip-by-tor onionaddress")
2020-05-22 19:24:46 +02:00
sys.exit(1)
2020-07-19 10:32:17 +01:00
# constants for standard services
SERVICE_LND_REST_API = "LND-REST-API"
SERVICE_LND_GRPC_API = "LND-GRPC-API"
SERVICE_LNBITS = "LNBITS"
SERVICE_BTCPAY = "BTCPAY"
2020-11-15 22:07:53 +01:00
SERVICE_SPHINX = "SPHINX"
2020-07-19 10:32:17 +01:00
2020-07-17 19:16:06 +01:00
#####################
# BASIC SETTINGS
#####################
2020-05-24 18:04:56 +02:00
session = requests.session()
2020-05-24 13:11:31 +02:00
if Path("/mnt/hdd/raspiblitz.conf").is_file():
2020-05-26 18:33:46 +02:00
cfg = RaspiBlitzConfig()
cfg.reload()
2020-07-17 19:16:06 +01:00
if cfg.chain.value == "test":
is_testnet = True
else:
is_testnet = False
ENV = "PROD"
2020-07-27 15:36:01 +02:00
DEFAULT_SHOPURL = "fulmo7x6yvgz6zs2b2ptduvzwevxmizhq23klkenslt5drxx2physlqd.onion"
# DEFAULT_SHOPURL = "ip2tor.fulmo.org"
2020-07-17 19:16:06 +01:00
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_TLS_PATH = "/mnt/hdd/app-data/lnd/tls.cert"
2020-05-24 18:38:09 +02:00
# make sure to make requests thru TOR 127.0.0.1:9050
2020-07-17 19:16:06 +01:00
session.proxies = {'http': 'socks5h://127.0.0.1:9050', 'https': 'socks5h://127.0.0.1:9050'}
SUBSCRIPTIONS_FILE = "/mnt/hdd/app-data/subscriptions/subscriptions.toml"
2020-05-24 13:11:31 +02:00
else:
2020-07-17 19:16:06 +01:00
ENV = "DEV"
2020-05-24 13:11:31 +02:00
print("# blitz.ip2tor.py (development env)")
2020-07-27 15:36:01 +02:00
DEFAULT_SHOPURL = "fulmo7x6yvgz6zs2b2ptduvzwevxmizhq23klkenslt5drxx2physlqd.onion"
# DEFAULT_SHOPURL = "ip2tor.fulmo.org"
2020-07-17 19:16:06 +01:00
LND_IP = "192.168.178.95"
LND_ADMIN_MACAROON_PATH = "/Users/rotzoll/Downloads/RaspiBlitzCredentials/admin.macaroon"
LND_TLS_PATH = "/Users/rotzoll/Downloads/RaspiBlitzCredentials/tls.cert"
SUBSCRIPTIONS_FILE = "/Users/rotzoll/Downloads/RaspiBlitzCredentials/subscriptions.toml"
2020-07-12 16:21:43 +01:00
is_testnet = False
2020-07-17 19:16:06 +01:00
#####################
# HELPER FUNCTIONS
#####################
2020-05-25 00:19:32 +02:00
2020-05-24 13:11:31 +02:00
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
2020-07-17 19:16:06 +01:00
2020-05-25 19:18:34 +02:00
def handleException(e):
if isinstance(e, BlitzError):
2020-07-19 10:32:17 +01:00
eprint(e.details)
eprint(e.org)
print("error='{0}'".format(e.short))
2020-05-25 19:18:34 +02:00
else:
eprint(e)
print("error='{0}'".format(str(e)))
sys.exit(1)
2020-07-17 19:16:06 +01:00
2020-05-24 13:11:31 +02:00
def parseDate(datestr):
2020-07-17 19:16:06 +01:00
return datetime.strptime(datestr, "%Y-%m-%dT%H:%M:%SZ")
2020-05-24 13:11:31 +02:00
def secondsLeft(dateObj):
2020-07-17 19:16:06 +01:00
return round((dateObj - datetime.utcnow()).total_seconds())
2020-05-24 13:11:31 +02:00
# 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
2020-07-17 19:16:06 +01:00
if len(shopurlUserInput) < 3:
return ""
2020-05-24 13:11:31 +02:00
shopurlUserInput = shopurlUserInput.lower()
shopurlUserInput = shopurlUserInput.replace(" ", "")
shopurlUserInput = shopurlUserInput.replace("\n", "")
shopurlUserInput = shopurlUserInput.replace("\r", "")
# remove protocol from the beginning (if needed)
if shopurlUserInput.find("://") > 0:
2020-07-17 19:16:06 +01:00
shopurlUserInput = shopurlUserInput[shopurlUserInput.find("://") + 3:]
2020-05-24 13:11:31 +02:00
# remove all path after domain
if shopurlUserInput.find("/") > 0:
shopurlUserInput = shopurlUserInput[:shopurlUserInput.find("/")]
# add correct protocol again
2020-07-17 19:16:06 +01:00
if not shopurlUserInput.startswith("http://") and not shopurlUserInput.startswith("https://"):
2020-05-24 13:11:31 +02:00
if shopurlUserInput.endswith(".onion"):
shopurlUserInput = "http://{0}".format(shopurlUserInput)
else:
shopurlUserInput = "https://{0}".format(shopurlUserInput)
return shopurlUserInput
2020-05-22 19:24:46 +02:00
2020-05-25 00:19:32 +02:00
2020-07-17 19:16:06 +01:00
#####################
# IP2TOR API CALLS
#####################
2020-05-23 02:44:08 +02:00
2020-07-17 19:16:06 +01:00
def apiGetHosts(session, shopurl):
2020-05-23 02:44:08 +02:00
print("# apiGetHosts")
2020-07-17 19:16:06 +01:00
hosts = []
2020-05-23 02:44:08 +02:00
# make HTTP request
2020-07-17 19:16:06 +01:00
url = "{0}/api/v1/public/hosts/?is_testnet={1}".format(shopurl, int(is_testnet))
2020-05-23 02:44:08 +02:00
try:
2020-05-24 18:00:48 +02:00
response = session.get(url)
2020-05-23 02:44:08 +02:00
except Exception as e:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed HTTP request", {'url': url}, e)
if response.status_code == 404:
raise BlitzError("failed HTTP code, cancel the old tor bridge subscription and create a new one",
{'status_code': response.status_code})
2020-05-23 02:44:08 +02:00
if response.status_code != 200:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed HTTP code", {'status_code': response.status_code})
2020-07-17 19:16:06 +01:00
2020-05-23 02:44:08 +02:00
# parse & validate data
try:
jData = json.loads(response.content)
except Exception as e:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed JSON parsing", {'content': response.content}, e)
2020-05-23 02:44:08 +02:00
if not isinstance(jData, list):
2020-07-19 10:32:17 +01:00
raise BlitzError("hosts not list", {'content': response.content})
2020-05-23 02:44:08 +02:00
for idx, hostEntry in enumerate(jData):
try:
# ignore if not offering tor bridge
2020-07-17 19:16:06 +01:00
if not hostEntry['offers_tor_bridges']:
continue
2020-05-23 02:44:08 +02:00
# ignore if duration is less than an hour
2020-07-17 19:16:06 +01:00
if hostEntry['tor_bridge_duration'] < 3600:
continue
2020-05-23 02:44:08 +02:00
# add duration per hour value
2020-07-17 19:16:06 +01:00
hostEntry['tor_bridge_duration_hours'] = math.floor(hostEntry['tor_bridge_duration'] / 3600)
2020-05-23 02:44:08 +02:00
# ignore if prices are negative or below one sat (maybe msats later)
2020-07-17 19:16:06 +01:00
if hostEntry['tor_bridge_price_initial'] < 1000:
continue
if hostEntry['tor_bridge_price_extension'] < 1000:
continue
2020-05-23 02:44:08 +02:00
# add price in sats
2020-07-17 19:16:06 +01:00
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)
2020-05-23 02:44:08 +02:00
# ignore name is less then 3 chars
2020-07-17 19:16:06 +01:00
if len(hostEntry['name']) < 3:
continue
2020-05-23 02:44:08 +02:00
# ignore id with zero value
2020-07-17 19:16:06 +01:00
if len(hostEntry['id']) < 1:
continue
2020-05-23 02:44:08 +02:00
# shorten names to 20 chars max
hostEntry['name'] = hostEntry['name'][:20]
except Exception as e:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed host entry pasring", hostEntry, e)
2020-05-23 02:44:08 +02:00
hosts.append(hostEntry)
2020-07-17 19:16:06 +01:00
2020-05-23 02:44:08 +02:00
print("# found {0} valid torbridge hosts".format(len(hosts)))
return hosts
2020-07-17 19:16:06 +01:00
def apiPlaceOrderNew(session, shopurl, hostid, toraddressWithPort):
2020-05-24 13:11:31 +02:00
print("# apiPlaceOrderNew")
2020-05-23 02:44:08 +02:00
2020-07-17 19:16:06 +01:00
url = "{0}/api/v1/public/order/".format(shopurl)
postData = {
'product': "tor_bridge",
'host_id': hostid,
'tos_accepted': True,
2020-07-29 09:13:30 +02:00
'comment': 'RaspiBlitz',
2020-07-17 19:16:06 +01:00
'target': toraddressWithPort,
'public_key': ''
}
2020-05-23 02:44:08 +02:00
try:
2020-05-25 19:18:34 +02:00
response = session.post(url, data=postData)
2020-05-23 02:44:08 +02:00
except Exception as e:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed HTTP request", {'url': url}, e)
2020-07-12 21:35:57 +02:00
if response.status_code == 420:
2020-07-19 10:32:17 +01:00
raise BlitzError("forwarding this address was rejected", {'status_code': response.status_code})
2020-05-23 02:44:08 +02:00
if response.status_code != 201:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed HTTP code", {'status_code': response.status_code})
2020-05-23 02:44:08 +02:00
# parse & validate data
try:
jData = json.loads(response.content)
2020-05-23 13:49:13 +02:00
if len(jData['id']) == 0:
print("error='MISSING ID'")
return
2020-05-23 02:44:08 +02:00
except Exception as e:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed JSON parsing", {'status_code': response.status_code}, e)
2020-05-23 02:44:08 +02:00
2020-05-23 13:49:13 +02:00
return jData['id']
2020-05-24 13:11:31 +02:00
2020-07-17 19:16:06 +01:00
def apiPlaceOrderExtension(session, shopurl, bridgeid):
2020-05-24 13:11:31 +02:00
print("# apiPlaceOrderExtension")
2020-07-17 19:16:06 +01:00
url = "{0}/api/v1/public/tor_bridges/{1}/extend/".format(shopurl, bridgeid)
2020-05-24 13:11:31 +02:00
try:
response = session.post(url)
except Exception as e:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed HTTP request", {'url': url}, e)
2020-07-12 21:35:57 +02:00
if response.status_code == 420:
2020-07-19 10:32:17 +01:00
raise BlitzError("forwarding this address was rejected", {'status_code': response.status_code})
2020-05-24 13:11:31 +02:00
if response.status_code != 200 and response.status_code != 201:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed HTTP code", {'status_code': response.status_code})
2020-05-24 13:11:31 +02:00
# parse & validate data
2020-05-26 02:32:21 +02:00
print("# parse")
2020-05-24 13:11:31 +02:00
try:
jData = json.loads(response.content)
if len(jData['po_id']) == 0:
print("error='MISSING ID'")
return
except Exception as e:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed JSON parsing", {'content': response.content}, e)
2020-05-24 13:11:31 +02:00
return jData['po_id']
2020-05-23 13:49:13 +02:00
2020-07-19 10:32:17 +01:00
def apiGetOrder(session, shopurl, orderid) -> dict:
2020-05-23 13:49:13 +02:00
print("# apiGetOrder")
# make HTTP request
2020-07-17 19:16:06 +01:00
url = "{0}/api/v1/public/pos/{1}/".format(shopurl, orderid)
2020-05-23 13:49:13 +02:00
try:
2020-05-25 19:18:34 +02:00
response = session.get(url)
2020-05-23 13:49:13 +02:00
except Exception as e:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed HTTP request", {'url': url}, e)
2020-05-23 13:49:13 +02:00
if response.status_code != 200:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed HTTP code", {'status_code': response.status_code})
2020-05-25 19:18:34 +02:00
2020-05-23 13:49:13 +02:00
# parse & validate data
try:
jData = json.loads(response.content)
if len(jData['item_details']) == 0:
2020-07-19 10:32:17 +01:00
raise BlitzError("missing item", {'content': response.content})
2020-05-23 13:49:13 +02:00
if len(jData['ln_invoices']) > 1:
2020-07-19 10:32:17 +01:00
raise BlitzError("more than one invoice", {'content': response.content})
2020-05-23 13:49:13 +02:00
except Exception as e:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed JSON parsing", {'content': response.content}, e)
2020-05-24 13:11:31 +02:00
return jData
2020-07-17 19:16:06 +01:00
def apiGetBridgeStatus(session, shopurl, bridgeid):
2020-05-24 13:11:31 +02:00
print("# apiGetBridgeStatus")
# make HTTP request
2020-07-17 19:16:06 +01:00
url = "{0}/api/v1/public/tor_bridges/{1}/".format(shopurl, bridgeid)
2020-05-24 13:11:31 +02:00
try:
2020-05-25 19:18:34 +02:00
response = session.get(url)
2020-05-24 13:11:31 +02:00
except Exception as e:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed HTTP request", {'url': url}, e)
2020-05-24 13:11:31 +02:00
if response.status_code != 200:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed HTTP code", {'status_code': response.status_code})
2020-05-24 13:11:31 +02:00
# parse & validate data
try:
jData = json.loads(response.content)
if len(jData['id']) == 0:
2020-07-19 10:32:17 +01:00
raise BlitzError("missing id", {'content': response.content})
2020-05-24 13:11:31 +02:00
except Exception as e:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed JSON parsing", {'content': response.content}, e)
2020-05-23 02:44:08 +02:00
2020-05-23 13:49:13 +02:00
return jData
2020-07-17 19:16:06 +01:00
#####################
# LND API CALLS
#####################
2020-05-25 00:19:32 +02:00
2020-05-23 13:49:13 +02:00
def lndDecodeInvoice(lnInvoiceString):
2020-05-24 13:11:31 +02:00
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:
2020-07-19 10:32:17 +01:00
raise BlitzError("zero invoice not allowed", {'invoice': lnInvoiceString})
2020-05-24 13:11:31 +02:00
except Exception as e:
2020-07-19 10:32:17 +01:00
raise BlitzError("failed LND invoice decoding", {'invoice': lnInvoiceString}, e)
2020-05-24 13:11:31 +02:00
return response
2020-07-17 19:16:06 +01:00
2020-05-24 13:11:31 +02:00
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:
2020-07-19 10:32:17 +01:00
raise BlitzError(response.payment_error, {'invoice': lnInvoiceString})
2020-05-24 13:11:31 +02:00
except Exception as e:
2020-07-19 10:32:17 +01:00
raise BlitzError("payment failed", {'invoice': lnInvoiceString}, e)
2020-05-23 13:49:13 +02:00
2020-05-24 13:11:31 +02:00
return response
2020-05-23 13:49:13 +02:00
2020-05-25 00:19:32 +02:00
2020-07-17 19:16:06 +01:00
#####################
# PROCESS FUNCTIONS
#####################
2020-05-25 00:19:32 +02:00
2020-07-17 19:16:06 +01:00
def shopList(shopUrl):
2020-05-25 19:53:05 +02:00
print("#### Getting available options from shop ...")
2020-07-17 19:16:06 +01:00
shopUrl = normalizeShopUrl(shopUrl)
2020-05-25 00:19:32 +02:00
return apiGetHosts(session, shopUrl)
2020-07-17 19:16:06 +01:00
def shopOrder(shopUrl, hostid, servicename, torTarget, duration, msatsFirst, msatsNext, description=""):
2021-08-27 03:59:21 -04:00
print("#### Placing order ...")
2020-07-17 19:16:06 +01:00
shopUrl = normalizeShopUrl(shopUrl)
2020-05-25 00:19:32 +02:00
orderid = apiPlaceOrderNew(session, shopUrl, hostid, torTarget)
2020-05-25 19:53:05 +02:00
print("#### Waiting until invoice is available ...")
2020-07-17 19:16:06 +01:00
loopCount = 0
2020-05-25 00:19:32 +02:00
while True:
2020-05-25 19:18:34 +02:00
time.sleep(2)
2020-07-17 19:16:06 +01:00
loopCount += 1
2020-05-25 00:19:32 +02:00
print("# Loop {0}".format(loopCount))
order = apiGetOrder(session, shopUrl, orderid)
2020-07-15 14:36:56 +02:00
if order['status'] == "R":
2020-07-17 19:16:06 +01:00
raise BlitzError("Subscription Rejected", order)
2020-05-25 19:18:34 +02:00
if len(order['ln_invoices']) > 0 and order['ln_invoices'][0]['payment_request'] is not None:
break
if loopCount > 60:
2020-05-25 19:18:34 +02:00
raise BlitzError("timeout on getting invoice", order)
2020-05-25 00:19:32 +02:00
# 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']
2020-05-25 19:53:05 +02:00
print("#### Decoding invoice and checking ..)")
2020-05-25 00:19:32 +02:00
print("# invoice: {0}".format(paymentRequestStr))
paymentRequestDecoded = lndDecodeInvoice(paymentRequestStr)
if paymentRequestDecoded is None: sys.exit()
2020-07-14 23:53:20 +02:00
print("# amount as advertised: {0} milliSats".format(msatsFirst))
print("# amount in invoice is: {0} milliSats".format(paymentRequestDecoded.num_msat))
2020-05-25 19:53:05 +02:00
if int(msatsFirst) < int(paymentRequestDecoded.num_msat):
2020-07-17 19:16:06 +01:00
raise BlitzError("invoice bigger amount than advertised",
"advertised({0}) invoice({1})".format(msatsFirst, paymentRequestDecoded.num_msat))
2020-05-25 00:19:32 +02:00
2020-05-25 19:53:05 +02:00
print("#### Paying invoice ...")
2020-05-25 00:19:32 +02:00
payedInvoice = lndPayInvoice(paymentRequestStr)
print('# OK PAYMENT SENT')
2020-05-25 19:53:05 +02:00
print("#### Waiting until bridge is ready ...")
2020-07-17 19:16:06 +01:00
loopCount = 0
2020-05-25 00:19:32 +02:00
while True:
2020-05-25 19:18:34 +02:00
time.sleep(3)
2020-07-17 19:16:06 +01:00
loopCount += 1
2020-05-25 00:19:32 +02:00
print("## Loop {0}".format(loopCount))
bridge = apiGetBridgeStatus(session, shopUrl, bridge_id)
2020-05-25 19:18:34 +02:00
if bridge['status'] == "A":
break
2020-07-18 21:14:23 +02:00
if bridge['status'] == "R":
break
2020-07-17 19:16:06 +01:00
if loopCount > 120:
2020-05-25 19:18:34 +02:00
raise BlitzError("timeout bridge not getting ready", bridge)
2020-07-17 19:16:06 +01:00
2020-07-16 18:16:12 +02:00
print("#### Check if port is valid ...")
2020-07-17 20:25:32 +01:00
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)
2021-03-23 13:49:45 +01:00
if bridge_port < 1 or bridge_port > 65535:
raise BlitzError("invalid port (not a valid tcp port)", bridge)
2020-07-16 18:16:12 +02:00
2020-05-25 19:53:05 +02:00
print("#### Check if duration delivered is as advertised ...")
2020-05-26 02:32:21 +02:00
contract_breached = False
warning_text = ""
2020-07-17 19:16:06 +01:00
secondsDelivered = secondsLeft(parseDate(bridge['suspend_after']))
2020-05-25 00:19:32 +02:00
print("# delivered({0}) promised({1})".format(secondsDelivered, duration))
2020-05-25 19:53:05 +02:00
if (secondsDelivered + 600) < int(duration):
2020-05-26 02:32:21 +02:00
contract_breached = True
warning_text = "delivered duration shorter than advertised"
2020-07-18 21:14:23 +02:00
if bridge['status'] == "R":
contract_breached = True
try:
warningTXT = "rejected: {0}".format(bridge['message'])
except Exception as e:
warningTXT = "rejected: n/a"
2020-05-26 02:32:21 +02:00
# create subscription data for storage
2020-07-17 19:16:06 +01:00
subscription = dict()
2020-05-26 14:22:27 +02:00
subscription['type'] = "ip2tor-v1"
2020-05-26 02:32:21 +02:00
subscription['id'] = bridge['id']
2020-05-26 23:29:17 +02:00
subscription['name'] = servicename
2020-05-26 02:32:21 +02:00
subscription['shop'] = shopUrl
2020-07-18 21:14:23 +02:00
subscription['active'] = not contract_breached
2020-05-26 02:32:21 +02:00
subscription['ip'] = bridge_ip
2020-07-17 20:25:32 +01:00
subscription['port'] = bridge_port
2020-05-26 02:32:21 +02:00
subscription['duration'] = int(duration)
subscription['price_initial'] = int(msatsFirst)
subscription['price_extension'] = int(msatsNext)
subscription['price_total'] = int(paymentRequestDecoded.num_msat)
2020-07-17 19:16:06 +01:00
subscription['time_created'] = str(datetime.now().strftime("%Y-%m-%d %H:%M"))
subscription['time_lastupdate'] = str(datetime.now().strftime("%Y-%m-%d %H:%M"))
2020-05-26 02:32:21 +02:00
subscription['suspend_after'] = bridge['suspend_after']
2020-05-26 14:22:27 +02:00
subscription['description'] = str(description)
2020-05-26 02:32:21 +02:00
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))
2020-05-26 02:32:21 +02:00
if Path(SUBSCRIPTIONS_FILE).is_file():
print("# load toml file")
subscriptions = toml.load(SUBSCRIPTIONS_FILE)
else:
print("# new toml file")
subscriptions = {}
2020-07-13 12:58:35 +02:00
if "subscriptions_ip2tor" not in subscriptions:
2020-05-26 14:22:27 +02:00
subscriptions['subscriptions_ip2tor'] = []
subscriptions['subscriptions_ip2tor'].append(subscription)
subscriptions['shop_ip2tor'] = shopUrl
2020-05-26 02:32:21 +02:00
with open(SUBSCRIPTIONS_FILE, 'w') as writer:
writer.write(toml.dumps(subscriptions))
writer.close()
2020-05-26 02:32:21 +02:00
except Exception as e:
eprint(e)
2020-07-19 10:32:17 +01:00
raise BlitzError("fail on subscription storage", subscription, e)
2020-05-25 00:19:32 +02:00
2020-07-17 20:25:32 +01:00
print("# OK - BRIDGE READY: {0}:{1} -> {2}".format(bridge_ip, bridge_port, torTarget))
2020-05-26 02:32:21 +02:00
return subscription
2020-07-17 19:16:06 +01:00
def subscriptionExtend(shopUrl, bridgeid, durationAdvertised, msatsNext, bridge_suspendafter):
warningTXT = ""
contract_breached = False
2020-05-25 00:19:32 +02:00
2020-05-25 19:53:05 +02:00
print("#### Placing extension order ...")
2020-07-17 19:16:06 +01:00
shopUrl = normalizeShopUrl(shopUrl)
2020-05-25 00:19:32 +02:00
orderid = apiPlaceOrderExtension(session, shopUrl, bridgeid)
2020-05-25 19:53:05 +02:00
print("#### Waiting until invoice is available ...")
2020-07-17 19:16:06 +01:00
loopCount = 0
2020-05-25 00:19:32 +02:00
while True:
2020-05-25 19:18:34 +02:00
time.sleep(2)
2020-07-17 19:16:06 +01:00
loopCount += 1
2020-05-25 00:19:32 +02:00
print("## Loop {0}".format(loopCount))
order = apiGetOrder(session, shopUrl, orderid)
2020-05-25 19:18:34 +02:00
if len(order['ln_invoices']) > 0 and order['ln_invoices'][0]['payment_request'] is not None:
break
if loopCount > 120:
2020-05-25 19:18:34 +02:00
raise BlitzError("timeout on getting invoice", order)
2020-07-17 19:16:06 +01:00
2020-05-25 00:19:32 +02:00
paymentRequestStr = order['ln_invoices'][0]['payment_request']
2020-05-25 19:53:05 +02:00
print("#### Decoding invoice and checking ..)")
2020-05-25 00:19:32 +02:00
print("# invoice: {0}".format(paymentRequestStr))
paymentRequestDecoded = lndDecodeInvoice(paymentRequestStr)
if paymentRequestDecoded is None: sys.exit()
2020-07-14 23:53:20 +02:00
print("# amount as advertised: {0} milliSats".format(msatsNext))
print("# amount in invoice is: {0} milliSats".format(paymentRequestDecoded.num_msat))
2020-05-25 19:53:05 +02:00
if int(msatsNext) < int(paymentRequestDecoded.num_msat):
2020-07-17 19:16:06 +01:00
raise BlitzError("invoice bigger amount than advertised",
"advertised({0}) invoice({1})".format(msatsNext, paymentRequestDecoded.num_msat))
2020-05-25 00:19:32 +02:00
2020-05-25 19:53:05 +02:00
print("#### Paying invoice ...")
2020-05-25 00:19:32 +02:00
payedInvoice = lndPayInvoice(paymentRequestStr)
2020-05-26 02:32:21 +02:00
print("#### Check if bridge was extended ...")
2020-07-17 19:16:06 +01:00
bridge = None
loopCount = 0
2020-05-25 00:19:32 +02:00
while True:
2020-05-25 19:18:34 +02:00
time.sleep(3)
2020-07-17 19:16:06 +01:00
loopCount += 1
2020-05-25 00:19:32 +02:00
print("## Loop {0}".format(loopCount))
2020-07-17 19:16:06 +01:00
try:
2020-05-26 02:32:21 +02:00
bridge = apiGetBridgeStatus(session, shopUrl, bridgeid)
2020-07-18 21:14:23 +02:00
if bridge['status'] == "R":
contract_breached = True
try:
warningTXT = "rejected: {0}".format(bridge['message'])
except Exception as e:
warningTXT = "rejected: n/a"
break
2020-05-26 02:32:21 +02:00
if bridge['suspend_after'] != bridge_suspendafter:
break
except Exception as e:
eprint(e)
print("# EXCEPTION on apiGetBridgeStatus")
if loopCount > 240:
2020-07-17 19:16:06 +01:00
warningTXT = "timeout on last payed extension"
contract_breached = True
2020-05-25 19:18:34 +02:00
break
2020-05-26 02:32:21 +02:00
2020-07-17 19:16:06 +01:00
if bridge and not contract_breached:
2020-05-26 02:32:21 +02:00
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))
2020-05-26 02:32:21 +02:00
subscriptions = toml.load(SUBSCRIPTIONS_FILE)
2020-05-26 14:22:27 +02:00
for idx, subscription in enumerate(subscriptions['subscriptions_ip2tor']):
2020-05-26 02:32:21 +02:00
if subscription['id'] == bridgeid:
subscription['suspend_after'] = str(bridge['suspend_after'])
2020-07-17 19:16:06 +01:00
subscription['time_lastupdate'] = str(datetime.now().strftime("%Y-%m-%d %H:%M"))
2020-05-26 02:32:21 +02:00
subscription['price_total'] += int(paymentRequestDecoded.num_msat)
subscription['contract_breached'] = contract_breached
subscription['warning'] = warningTXT
if contract_breached:
2020-05-26 02:48:05 +02:00
subscription['active'] = False
2020-05-26 02:32:21 +02:00
with open(SUBSCRIPTIONS_FILE, 'w') as writer:
writer.write(toml.dumps(subscriptions))
writer.close()
break
2020-07-17 19:16:06 +01:00
2020-05-26 02:32:21 +02:00
except Exception as e:
eprint(e)
2020-07-19 10:32:17 +01:00
raise BlitzError("fail on subscription storage", org=e)
2020-05-26 02:32:21 +02:00
2020-05-25 00:19:32 +02:00
print("# BRIDGE GOT EXTENDED: {0} -> {1}".format(bridge_suspendafter, bridge['suspend_after']))
2020-05-23 13:49:13 +02:00
2020-07-17 19:16:06 +01:00
2020-05-25 19:18:34 +02:00
def menuMakeSubscription(blitzServiceName, torAddress, torPort):
2020-07-17 19:16:06 +01:00
# late imports - so that rest of script can run also if dependency is not available
from dialog import Dialog
2020-05-25 00:19:32 +02:00
2020-05-25 19:18:34 +02:00
torTarget = "{0}:{1}".format(torAddress, torPort)
2020-05-25 00:19:32 +02:00
2020-05-25 19:18:34 +02:00
############################
# PHASE 1: Enter Shop URL
2020-05-25 00:19:32 +02:00
2020-05-26 02:32:21 +02:00
# see if user had before entered another shop of preference
2020-05-25 00:19:32 +02:00
shopurl = DEFAULT_SHOPURL
2020-05-26 20:25:34 +02:00
try:
2020-05-26 02:32:21 +02:00
subscriptions = toml.load(SUBSCRIPTIONS_FILE)
2020-05-26 20:25:34 +02:00
shopurl = subscriptions['shop_ip2tor']
print("# using last shop url set in subscriptions.toml")
except Exception as e:
print("# using default shop url")
2020-05-26 02:32:21 +02:00
# remove https:// from shop url (to keep it short)
if shopurl.find("://") > 0:
shopurl = shopurl[shopurl.find("://") + 3:]
2020-05-25 01:10:03 +02:00
while True:
# input shop url
2020-07-17 19:16:06 +01:00
d = Dialog(dialog="dialog", autowidgetsize=True)
2020-05-25 02:47:19 +02:00
d.set_background_title("Select IP2TOR Bridge Shop (communication secured thru TOR)")
code, text = d.inputbox(
"Enter Address of the IP2TOR Shop (OR JUST PRESS OK):",
height=10, width=72, init=shopurl,
2020-05-25 02:47:19 +02:00
title="Shop Address")
2020-05-25 01:10:03 +02:00
# if user canceled
2020-07-17 19:16:06 +01:00
if code != d.OK:
sys.exit(0)
2020-05-25 01:10:03 +02:00
# get host list from shop
2020-05-25 01:17:57 +02:00
shopurl = text
2020-05-25 01:27:16 +02:00
os.system('clear')
2020-05-25 19:18:34 +02:00
try:
hosts = shopList(shopurl)
except Exception as e:
2020-05-25 01:10:03 +02:00
# shopurl not working
2020-11-15 22:07:53 +01:00
eprint(e)
time.sleep(3)
2020-07-17 19:16:06 +01:00
Dialog(dialog="dialog", autowidgetsize=True).msgbox('''
2020-05-25 01:30:04 +02:00
Cannot reach a shop under that address.
Please check domain or cancel dialog.
2020-07-17 19:16:06 +01:00
''', title="ERROR")
2020-05-25 19:18:34 +02:00
else:
# when shop is empty
if len(hosts) == 0:
2020-07-17 19:16:06 +01:00
Dialog(dialog="dialog", autowidgetsize=True).msgbox('''
2020-05-25 02:47:19 +02:00
The shop has no available offers at the moment.
Try again later, enter another address or cancel.
2020-07-17 19:16:06 +01:00
''', title="ERROR")
2020-05-25 01:10:03 +02:00
# ok we got hosts - continue
2020-07-17 19:16:06 +01:00
else:
break
2020-05-25 19:18:34 +02:00
###############################
# PHASE 2: SELECT SUBSCRIPTION
2020-05-25 00:19:32 +02:00
2020-05-26 17:24:29 +02:00
# create menu to select shop
2020-07-17 19:16:06 +01:00
host = None
2020-05-25 00:19:32 +02:00
choices = []
for idx, hostEntry in enumerate(hosts):
2020-07-17 19:16:06 +01:00
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'])
)
)
2020-05-25 01:10:03 +02:00
while True:
2020-05-25 00:19:32 +02:00
2020-05-25 01:10:03 +02:00
# show menu with options
2020-07-17 19:16:06 +01:00
d = Dialog(dialog="dialog", autowidgetsize=True)
2020-05-25 02:47:19 +02:00
d.set_background_title("IP2TOR Bridge Shop: {0}".format(shopurl))
code, tag = d.menu(
"Following TOR bridge hosts are available. Select for details:",
choices=choices, title="Available Subscriptions")
2020-07-17 19:16:06 +01:00
2020-05-25 19:18:34 +02:00
# if user cancels
2020-07-19 10:32:17 +01:00
if code != d.OK:
sys.exit(0)
2020-05-25 01:10:03 +02:00
# get data of selected
seletedIndex = int(tag)
2020-05-25 02:47:19 +02:00
host = hosts[seletedIndex]
2020-05-25 01:10:03 +02:00
2020-05-25 02:47:19 +02:00
# optimize content for display
2020-05-25 02:49:25 +02:00
if len(host['terms_of_service']) == 0: host['terms_of_service'] = "-"
if len(host['terms_of_service_url']) == 0: host['terms_of_service_url'] = "-"
2020-05-25 02:47:19 +02:00
2020-05-25 01:10:03 +02:00
# show details of selected
2020-07-17 19:16:06 +01:00
d = Dialog(dialog="dialog", autowidgetsize=True)
2020-05-25 02:47:19 +02:00
d.set_background_title("IP2TOR Bridge Offer Details: {0}".format(shopurl))
2020-07-17 19:16:06 +01:00
text = '''
2020-05-25 19:38:15 +02:00
The subscription would renew every {0} hours.
The first time it would cost: {1} sats
Every next time it would cost: {2} sats
2020-05-25 02:47:19 +02:00
If you AGREE you will subscribe to this service.
2020-05-25 03:19:38 +02:00
You will get a port on the IP {3} that will
2020-05-26 23:37:37 +02:00
forward to your RaspiBlitz TOR address for '{7}':
2020-05-25 03:19:38 +02:00
{4}
2020-05-25 02:47:19 +02:00
You can cancel the subscription anytime under
the "SUBSCRIPTONS" menu on your RaspiBlitz.
There will be no refunds for not used hours.
There is no guarantee for quality of service.
The service has the following additional terms:
2020-05-25 03:19:38 +02:00
{5}
2020-05-25 02:47:19 +02:00
More information on the service you can find under:
2020-05-25 03:19:38 +02:00
{6}
2020-05-25 02:47:19 +02:00
'''.format(
2020-07-17 19:16:06 +01:00
host['tor_bridge_duration_hours'],
host['tor_bridge_price_initial_sats'],
host['tor_bridge_price_extension_sats'],
host['ip'],
torTarget,
host['terms_of_service'],
host['terms_of_service_url'],
blitzServiceName
2020-05-26 23:37:37 +02:00
)
2020-05-25 00:19:32 +02:00
2020-07-17 19:16:06 +01:00
code = d.msgbox(text, title=host['name'], ok_label="Back", extra_button=True, extra_label="AGREE", width=75,
height=30)
2020-05-25 03:44:30 +02:00
# if user AGREED break loop and continue with selected host
2020-07-19 10:32:17 +01:00
if code == "extra":
break
2020-05-25 01:10:03 +02:00
2020-05-25 19:18:34 +02:00
############################
# PHASE 3: Make Subscription
2020-05-26 14:22:27 +02:00
description = "{0} / {1} / {2}".format(host['name'], host['terms_of_service'], host['terms_of_service_url'])
2020-05-25 19:18:34 +02:00
try:
2020-05-25 20:36:03 +02:00
os.system('clear')
2020-07-17 19:16:06 +01:00
subscription = shopOrder(shopurl, host['id'], blitzServiceName, torTarget, host['tor_bridge_duration'],
host['tor_bridge_price_initial'], host['tor_bridge_price_extension'], description)
2020-05-25 19:18:34 +02:00
except BlitzError as be:
2020-07-17 19:16:06 +01:00
exitcode = 0
2020-07-15 14:19:16 +02:00
2020-07-19 10:32:17 +01:00
try:
message = be.details['message']
except KeyError:
message = ""
2020-07-18 12:24:07 +01:00
2020-07-19 10:32:17 +01:00
if (be.short == "timeout on waiting for extending bridge" or
be.short == "fail on subscription storage" or
be.short == "invalid port" or
be.short == "timeout bridge not getting ready"):
2020-05-25 01:10:03 +02:00
2020-07-17 19:16:06 +01:00
# error happened after payment
exitcode = Dialog(dialog="dialog", autowidgetsize=True).msgbox('''
2020-05-25 19:18:34 +02:00
You DID PAY the initial fee.
But the service was not able to provide service.
Subscription will be ignored.
2020-07-18 12:24:07 +01:00
2020-06-14 12:12:46 +02:00
Error: {0}
2020-07-18 12:24:07 +01:00
Message: {1}
2020-07-19 10:32:17 +01:00
'''.format(be.short, message), title="Error on Subscription", extra_button=True, extra_label="Details")
2020-05-25 19:18:34 +02:00
else:
2020-07-17 19:16:06 +01:00
# error happened before payment
exitcode = Dialog(dialog="dialog", autowidgetsize=True).msgbox('''
2020-05-25 19:18:34 +02:00
You DID NOT PAY the initial fee.
The service was not able to provide service.
Subscription will be ignored.
2020-07-18 12:24:07 +01:00
2020-05-25 19:18:34 +02:00
Error: {0}
2020-07-18 12:24:07 +01:00
Message: {1}
2020-07-19 10:32:17 +01:00
'''.format(be.short, message), title="Error on Subscription", extra_button=True, extra_label="Details")
2020-07-15 14:19:16 +02:00
# show more details (when user used extra button)
2020-07-15 14:20:43 +02:00
if exitcode == Dialog.EXTRA:
2020-07-15 14:19:16 +02:00
os.system('clear')
print('###### ERROR DETAIL FOR DEBUG #######')
print("")
2020-07-15 14:32:06 +02:00
print("Error Short:")
2020-07-19 10:32:17 +01:00
print(be.short)
2020-07-15 14:19:16 +02:00
print('Shop:')
2020-07-15 14:29:02 +02:00
print(shopurl)
print('Bridge:')
print(str(host))
2020-07-15 14:19:16 +02:00
print("Error Detail:")
2020-07-19 10:32:17 +01:00
print(be.details)
2020-07-15 14:19:16 +02:00
print("")
input("Press Enter to continue ...")
2020-07-17 19:16:06 +01:00
2020-07-15 14:19:16 +02:00
sys.exit(1)
2020-05-25 19:18:34 +02:00
except Exception as e:
2020-07-17 19:16:06 +01:00
# unknown error happened
2020-07-15 14:19:16 +02:00
os.system('clear')
print('###### EXCEPTION DETAIL FOR DEBUG #######')
print("")
print('Shop:')
2020-07-15 14:29:02 +02:00
print(shopurl)
print('Bridge:')
print(str(host))
2020-07-15 14:19:16 +02:00
print("EXCEPTION:")
print(str(e))
print("")
input("Press Enter to continue ...")
sys.exit(1)
2020-05-25 19:18:34 +02:00
2020-07-17 19:16:06 +01:00
# if LND REST or LND GRPC service ... add bridge IP to TLS
2020-07-19 10:32:17 +01:00
if blitzServiceName == SERVICE_LND_REST_API or blitzServiceName == SERVICE_LND_GRPC_API:
2020-05-28 15:08:12 +02:00
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 mainnet tls")
os.system("sudo /home/admin/config.scripts/lnd.credentials.sh sync mainnet")
2020-05-28 15:08:12 +02:00
2020-05-25 19:18:34 +02:00
# warn user if not delivered as advertised
2020-05-26 02:32:21 +02:00
if subscription['contract_breached']:
2020-07-17 19:16:06 +01:00
Dialog(dialog="dialog", autowidgetsize=True).msgbox('''
2020-05-25 19:18:34 +02:00
The service was payed & delivered, but RaspiBlitz detected:
{0}
You may want to consider to cancel the subscription later.
2020-07-17 19:16:06 +01:00
'''.format(subscription['warning'], title="Warning"))
2020-05-25 19:18:34 +02:00
2020-07-14 23:06:09 +02:00
# decide if https:// address
2020-07-17 19:16:06 +01:00
protocol = ""
2020-07-19 10:32:17 +01:00
if blitzServiceName == SERVICE_LNBITS:
2020-07-17 19:16:06 +01:00
protocol = "https://"
2020-07-14 23:06:09 +02:00
2020-05-25 19:18:34 +02:00
# Give final result feedback to user
2020-07-17 19:16:06 +01:00
Dialog(dialog="dialog", autowidgetsize=True).msgbox('''
2020-05-25 19:18:34 +02:00
OK your service is ready & your subscription is active.
You payed {0} sats for the first {1} hours.
2020-05-26 02:32:21 +02:00
Next AUTOMATED PAYMENTS will be {2} sats each.
2020-05-25 19:18:34 +02:00
Your service '{3}' should now publicly be reachable under:
2020-07-14 23:06:09 +02:00
{6}{4}:{5}
2020-05-25 19:18:34 +02:00
Please test now if the service is performing as promised.
If not - dont forget to cancel the subscription under:
2020-05-27 00:36:11 +02:00
MAIN MENU > Manage Subscriptions > My Subscriptions
2020-05-25 20:42:29 +02:00
'''.format(
host['tor_bridge_price_initial_sats'],
host['tor_bridge_duration_hours'],
host['tor_bridge_price_extension_sats'],
blitzServiceName,
2020-05-26 02:32:21 +02:00
subscription['ip'],
2020-07-14 23:56:20 +02:00
subscription['port'],
protocol),
2020-07-17 19:16:06 +01:00
title="Subscription Active"
)
2020-05-25 19:18:34 +02:00
2020-07-17 19:16:06 +01:00
#####################
# COMMANDS
#####################
2020-05-25 19:18:34 +02:00
###############
2020-05-26 15:37:43 +02:00
# CREATE SSH DIALOG
2020-05-25 19:18:34 +02:00
# use for ssh shell menu
###############
2020-07-19 10:32:17 +01:00
def create_ssh_dialog():
2020-05-26 15:37:43 +02:00
# check parameters
try:
2020-07-17 19:16:06 +01:00
if len(sys.argv) <= 4:
2020-07-19 10:32:17 +01:00
raise BlitzError("incorrect parameters")
2020-05-26 15:37:43 +02:00
except Exception as e:
handleException(e)
2020-05-25 19:18:34 +02:00
2020-07-17 19:16:06 +01:00
servicename = sys.argv[2]
toraddress = sys.argv[3]
port = sys.argv[4]
2020-05-26 15:37:43 +02:00
menuMakeSubscription(servicename, toraddress, port)
2020-05-24 13:11:31 +02:00
sys.exit()
2020-07-19 10:32:17 +01:00
2020-05-25 00:19:32 +02:00
###############
# SHOP LIST
# call from web interface
2020-05-26 15:37:43 +02:00
###############
2020-07-19 10:32:17 +01:00
def shop_list():
2020-05-25 00:19:32 +02:00
# check parameters
try:
2020-07-17 19:16:06 +01:00
if len(sys.argv) <= 2:
2020-07-19 10:32:17 +01:00
raise BlitzError("incorrect parameters")
2020-05-25 00:19:32 +02:00
except Exception as e:
2020-05-25 19:18:34 +02:00
handleException(e)
2020-05-25 00:19:32 +02:00
2020-07-17 19:16:06 +01:00
shopurl = sys.argv[2]
2020-05-25 19:18:34 +02:00
try:
2020-07-17 19:16:06 +01:00
# get data
2020-05-25 19:18:34 +02:00
hosts = shopList(shopurl)
2020-07-17 19:16:06 +01:00
# output is json list of hosts
print(json.dumps(hosts, indent=2))
2020-05-25 19:18:34 +02:00
except Exception as e:
handleException(e)
2020-05-25 00:19:32 +02:00
sys.exit(0)
2020-07-19 10:32:17 +01:00
2020-07-17 19:16:06 +01:00
##########################
2020-05-25 00:19:32 +02:00
# SHOP ORDER
# call from web interface
2020-07-17 19:16:06 +01:00
##########################
2020-07-19 10:32:17 +01:00
def shop_order():
2020-05-25 19:18:34 +02:00
# check parameters
try:
2020-07-17 19:16:06 +01:00
if len(sys.argv) <= 8:
2020-07-19 10:32:17 +01:00
raise BlitzError("incorrect parameters")
2020-05-25 19:18:34 +02:00
except Exception as e:
handleException(e)
2020-05-25 00:19:32 +02:00
2020-07-17 19:16:06 +01:00
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 = ""
2020-05-25 19:18:34 +02:00
# get data
try:
2020-05-26 14:22:27 +02:00
subscription = shopOrder(shopurl, hostid, servicename, toraddress, duration, msatsFirst, msatsNext, description)
2020-07-17 19:16:06 +01:00
# output json ordered bridge
print(json.dumps(subscription, indent=2))
sys.exit()
2020-05-25 19:18:34 +02:00
except Exception as e:
handleException(e)
2020-05-25 00:19:32 +02:00
2020-07-19 10:32:17 +01:00
2020-05-25 19:18:34 +02:00
#######################
# SUBSCRIPTIONS LIST
2020-07-17 19:16:06 +01:00
# call in intervals from background process
2020-05-25 19:18:34 +02:00
#######################
2020-07-19 10:32:17 +01:00
def subscriptions_list():
2020-05-25 19:18:34 +02:00
try:
2020-05-26 02:48:05 +02:00
2020-05-26 02:39:32 +02:00
if Path(SUBSCRIPTIONS_FILE).is_file():
os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE))
2020-05-26 02:39:32 +02:00
subs = toml.load(SUBSCRIPTIONS_FILE)
else:
subs = {}
2020-07-13 12:53:54 +02:00
if "subscriptions_ip2tor" not in subs:
2020-05-26 14:22:27 +02:00
subs['subscriptions_ip2tor'] = []
2020-07-13 12:35:56 +02:00
print(json.dumps(subs['subscriptions_ip2tor'], indent=2))
2020-07-17 19:16:06 +01:00
2020-05-25 19:18:34 +02:00
except Exception as e:
handleException(e)
2020-05-25 00:19:32 +02:00
#######################
# SUBSCRIPTIONS RENEW
2020-07-17 19:16:06 +01:00
# call in intervals from background process
2020-05-25 00:19:32 +02:00
#######################
2020-07-19 10:32:17 +01:00
def subscriptions_renew():
print("# RUNNING subscriptions-renew")
2020-05-25 19:18:34 +02:00
# check parameters
try:
2020-05-26 02:32:21 +02:00
secondsBeforeSuspend = int(sys.argv[2])
2020-07-17 19:16:06 +01:00
if secondsBeforeSuspend < 0:
secondsBeforeSuspend = 0
2020-05-25 19:18:34 +02:00
except Exception as e:
print("# running with secondsBeforeSuspend=0")
secondsBeforeSuspend = 0
2020-05-25 00:19:32 +02:00
2020-07-17 19:16:06 +01:00
# check if any active subscriptions are below the secondsBeforeSuspend - if yes extend
2020-05-25 19:18:34 +02:00
try:
2020-07-17 19:16:06 +01:00
2020-05-26 02:32:21 +02:00
if not Path(SUBSCRIPTIONS_FILE).is_file():
print("# no subscriptions")
sys.exit(0)
2020-07-17 19:16:06 +01:00
os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE))
2020-05-26 02:32:21 +02:00
subscriptions = toml.load(SUBSCRIPTIONS_FILE)
2020-05-26 14:22:27 +02:00
for idx, subscription in enumerate(subscriptions['subscriptions_ip2tor']):
2020-05-26 02:32:21 +02:00
try:
2020-05-26 14:22:27 +02:00
if subscription['active'] and subscription['type'] == "ip2tor-v1":
2020-05-26 02:32:21 +02:00
secondsToRun = secondsLeft(parseDate(subscription['suspend_after']))
if secondsToRun < secondsBeforeSuspend:
2020-07-17 19:16:06 +01:00
print("# RENEW: subscription {0} with {1} seconds to run".format(subscription['id'],
secondsToRun))
2020-05-26 02:32:21 +02:00
subscriptionExtend(
2020-07-17 19:16:06 +01:00
subscription['shop'],
subscription['id'],
subscription['duration'],
2020-05-26 02:32:21 +02:00
subscription['price_extension'],
subscription['suspend_after']
2020-07-17 19:16:06 +01:00
)
2020-05-26 02:32:21 +02:00
else:
2020-07-17 19:16:06 +01:00
print("# GOOD: subscription {0} with {1} "
"seconds to run".format(subscription['id'], secondsToRun))
2020-05-26 02:32:21 +02:00
except BlitzError as be:
# write error into subscription warning
subs = toml.load(SUBSCRIPTIONS_FILE)
2020-07-17 19:16:06 +01:00
for sub in subs['subscriptions_ip2tor']:
2020-05-26 02:32:21 +02:00
if sub['id'] == subscription['id']:
2020-07-19 10:32:17 +01:00
sub['warning'] = "Exception on Renew: {0}".format(be.short)
if be.short == "invoice bigger amount than advertised":
2020-07-17 19:16:06 +01:00
sub['contract_breached'] = True
sub['active'] = False
2020-05-26 02:32:21 +02:00
with open(SUBSCRIPTIONS_FILE, 'w') as writer:
writer.write(toml.dumps(subs))
writer.close()
break
2020-07-19 10:32:17 +01:00
print("# BLITZERROR on subscriptions-renew of subscription index {0}: {1}".format(idx, be.short))
print("# {0}".format(be.short))
2020-05-26 02:32:21 +02:00
except Exception as e:
print("# EXCEPTION on subscriptions-renew of subscription index {0}".format(idx))
eprint(e)
2020-07-17 19:16:06 +01:00
2020-05-25 19:18:34 +02:00
except Exception as e:
handleException(e)
2020-05-25 19:30:12 +02:00
# output - not needed only for debug logs
print("# DONE subscriptions-renew")
2020-07-19 10:32:17 +01:00
2020-05-25 19:18:34 +02:00
#######################
# SUBSCRIPTION CANCEL
2020-07-19 10:32:17 +01:00
# call in intervals from background process
2020-05-25 19:18:34 +02:00
#######################
2020-07-19 10:32:17 +01:00
def subscription_cancel():
2020-05-26 02:39:32 +02:00
# check parameters
2020-05-25 19:18:34 +02:00
try:
2020-07-17 19:16:06 +01:00
if len(sys.argv) <= 2:
2020-07-19 10:32:17 +01:00
raise BlitzError("incorrect parameters")
2020-05-26 02:39:32 +02:00
except Exception as e:
handleException(e)
2020-07-17 19:16:06 +01:00
subscriptionID = sys.argv[2]
2020-05-26 02:39:32 +02:00
2020-07-17 19:16:06 +01:00
try:
os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE))
2020-05-26 02:39:32 +02:00
subs = toml.load(SUBSCRIPTIONS_FILE)
newList = []
2020-05-26 14:22:27 +02:00
for idx, sub in enumerate(subs['subscriptions_ip2tor']):
2020-05-26 02:39:32 +02:00
if sub['id'] != subscriptionID:
newList.append(sub)
2020-05-26 14:22:27 +02:00
subs['subscriptions_ip2tor'] = newList
2020-05-26 02:39:32 +02:00
# persist change
with open(SUBSCRIPTIONS_FILE, 'w') as writer:
writer.write(toml.dumps(subs))
writer.close()
print(json.dumps(subs, indent=2))
2020-05-25 19:18:34 +02:00
except Exception as e:
handleException(e)
2020-05-25 00:19:32 +02:00
2020-05-26 15:22:46 +02:00
#######################
2020-07-19 10:32:17 +01:00
# GET ADDRESS BY SERVICE NAME
2020-05-26 15:22:46 +02:00
# gets called by other scripts to check if service has a ip2tor bridge address
# output is bash key/value style so that it can be imported with source
#######################
2020-07-19 10:32:17 +01:00
def subscription_by_service():
2020-05-26 15:22:46 +02:00
# check parameters
try:
2020-07-17 19:16:06 +01:00
if len(sys.argv) <= 2:
2020-07-19 10:32:17 +01:00
raise BlitzError("incorrect parameters")
2020-05-26 15:22:46 +02:00
except Exception as e:
handleException(e)
2020-07-19 10:32:17 +01:00
service_name = sys.argv[2]
2020-07-17 19:16:06 +01:00
2020-05-26 15:22:46 +02:00
try:
if os.path.isfile(SUBSCRIPTIONS_FILE):
subs = toml.load(SUBSCRIPTIONS_FILE)
for idx, sub in enumerate(subs['subscriptions_ip2tor']):
2020-07-19 10:32:17 +01:00
if sub['active'] and sub['name'] == service_name:
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)
2020-07-17 19:16:06 +01:00
2020-05-26 15:22:46 +02:00
print("error='not found'")
except Exception as e:
handleException(e)
2020-07-19 10:32:17 +01:00
sys.exit(1)
2020-05-26 15:22:46 +02:00
2020-07-14 19:31:43 +02:00
#######################
2020-07-19 10:32:17 +01:00
# GET IP BY ONION ADDRESS
2020-07-14 19:31:43 +02:00
# gets called by other scripts to check if a onion address as a IP2TOR bridge
# output is bash key/value style so that it can be imported with source
#######################
2020-07-19 10:32:17 +01:00
def ip_by_tor():
2020-07-14 19:31:43 +02:00
# check parameters
try:
2020-07-17 19:16:06 +01:00
if len(sys.argv) <= 2:
2020-07-19 10:32:17 +01:00
raise BlitzError("incorrect parameters")
2020-07-14 19:31:43 +02:00
except Exception as e:
handleException(e)
2020-07-17 19:16:06 +01:00
onion = sys.argv[2]
2020-07-14 19:31:43 +02:00
try:
if os.path.isfile(SUBSCRIPTIONS_FILE):
subs = toml.load(SUBSCRIPTIONS_FILE)
for idx, sub in enumerate(subs['subscriptions_ip2tor']):
2020-07-14 19:38:06 +02:00
if sub['active'] and (sub['tor'] == onion or sub['tor'].split(":")[0] == onion):
2020-07-14 19:31:43 +02:00
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)
2020-07-17 19:16:06 +01:00
2020-07-14 19:31:43 +02:00
print("error='not found'")
except Exception as e:
handleException(e)
2020-07-19 10:32:17 +01:00
sys.exit(1)
def main():
if sys.argv[1] == "create-ssh-dialog":
create_ssh_dialog()
elif sys.argv[1] == "shop-list":
shop_list()
elif sys.argv[1] == "shop-order":
shop_order()
elif sys.argv[1] == "subscriptions-list":
subscriptions_list()
elif sys.argv[1] == "subscriptions-renew":
subscriptions_renew()
elif sys.argv[1] == "subscription-cancel":
subscription_cancel()
elif sys.argv[1] == "subscription-by-service":
subscription_by_service()
elif sys.argv[1] == "ip-by-tor":
ip_by_tor()
else:
# unknown command
print("# unknown command")
2020-07-14 19:31:43 +02:00
2020-07-19 10:32:17 +01:00
if __name__ == '__main__':
main()