diff --git a/modules/__init__.py b/modules/__init__.py index d639c4fb..afb61e14 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -8,6 +8,7 @@ from modules.validator import ValidatorModule from modules.controller import ControllerModule from modules.liteserver import LiteserverModule +from modules.alert_bot import AlertBotModule MODES = { @@ -15,7 +16,8 @@ 'nominator-pool': NominatorPoolModule, 'single-nominator': SingleNominatorModule, 'liquid-staking': ControllerModule, - 'liteserver': LiteserverModule + 'liteserver': LiteserverModule, + 'alert-bot': AlertBotModule } @@ -55,6 +57,8 @@ class Setting: 'defaultCustomOverlaysUrl': Setting(None, 'https://ton-blockchain.github.io/fallback_custom_overlays.json', 'Default custom overlays config url'), 'debug': Setting(None, False, 'Debug mtc console mode. Prints Traceback on errors'), 'subscribe_tg_channel': Setting('validator', False, 'Disables warning about subscribing to the `TON STATUS` channel'), + 'BotToken': Setting('alert-bot', None, 'Alerting Telegram bot token'), + 'ChatId': Setting('alert-bot', None, 'Alerting Telegram chat id') } diff --git a/modules/alert_bot.py b/modules/alert_bot.py new file mode 100644 index 00000000..e9dafed3 --- /dev/null +++ b/modules/alert_bot.py @@ -0,0 +1,285 @@ +import dataclasses +import time +import requests + +from modules.module import MtcModule +from mypylib.mypylib import get_timestamp, print_table, color_print +from mytoncore import get_hostname +from mytonctrl.utils import timestamp2utcdatetime + + +@dataclasses.dataclass +class Alert: + severity: str + text: str + timeout: int + + +HOUR = 3600 +VALIDATION_PERIOD = 65536 +FREEZE_PERIOD = 32768 + + +ALERTS = { + "low_wallet_balance": Alert( + "low", + "Validator wallet {wallet} balance is low: {balance} TON.", + 18*HOUR + ), + "db_usage_80": Alert( + "high", + """TON DB usage > 80%. Clean the TON database: + https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming + or (and) set node\'s archive ttl to lower value.""", + 24*HOUR + ), + "db_usage_95": Alert( + "critical", + """TON DB usage > 95%. Disk is almost full, clean the TON database immediately: + https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming + or (and) set node\'s archive ttl to lower value.""", + 6*HOUR + ), + "low_efficiency": Alert( + "high", + """Validator efficiency is low: {efficiency}%.""", + VALIDATION_PERIOD // 3 + ), + "out_of_sync": Alert( + "critical", + "Node is out of sync on {sync} sec.", + 0 + ), + "service_down": Alert( + "critical", + "validator.service is down.", + 0 + ), + "adnl_connection_failed": Alert( + "high", + "ADNL connection to node failed", + 3*HOUR + ), + "zero_block_created": Alert( + "critical", + "Validator has not created any blocks in the last {hours} hours.", + VALIDATION_PERIOD // 3 + ), + "validator_slashed": Alert( + "high", + "Validator has been slashed in previous round for {amount} TON", + FREEZE_PERIOD + ), +} + + +class AlertBotModule(MtcModule): + + description = 'Telegram bot alerts' + default_value = False + + def __init__(self, ton, local, *args, **kwargs): + super().__init__(ton, local, *args, **kwargs) + self.validator_module = None + self.inited = False + self.hostname = None + self.token = None + self.chat_id = None + self.last_db_check = 0 + + def send_message(self, text: str): + if self.token is None: + raise Exception("send_message error: token is not initialized") + if self.chat_id is None: + raise Exception("send_message error: chat_id is not initialized") + request_url = f"https://api.telegram.org/bot{self.token}/sendMessage" + data = {'chat_id': self.chat_id, 'text': text, 'parse_mode': 'HTML'} + response = requests.post(request_url, data=data, timeout=3) + if response.status_code != 200: + raise Exception(f"send_message error: {response.text}") + response = response.json() + if not response['ok']: + raise Exception(f"send_message error: {response}") + + def send_alert(self, alert_name: str, *args, **kwargs): + if not self.alert_is_enabled(alert_name): + return + last_sent = self.get_alert_sent(alert_name) + time_ = timestamp2utcdatetime(int(time.time())) + alert = ALERTS.get(alert_name) + if alert is None: + raise Exception(f"Alert {alert_name} not found") + text = f''' +❗️ MyTonCtrl Alert {alert_name} ❗️ + +Hostname: {self.hostname} +Time: {time_} ({int(time.time())}) +Severity: {alert.severity} + +Alert text: +
{alert.text.format(*args, **kwargs)}
+''' + if time.time() - last_sent > alert.timeout: + self.send_message(text) + self.set_alert_sent(alert_name) + + def set_global_vars(self): + # set global vars for correct alerts timeouts for current network + config15 = self.ton.GetConfig15() + global VALIDATION_PERIOD, FREEZE_PERIOD + VALIDATION_PERIOD = config15["validatorsElectedFor"] + FREEZE_PERIOD = config15["stakeHeldFor"] + + def init(self): + if not self.ton.get_mode_value('alert-bot'): + return + self.token = self.ton.local.db.get("BotToken") + self.chat_id = self.ton.local.db.get("ChatId") + if self.token is None or self.chat_id is None: + raise Exception("BotToken or ChatId is not set") + from modules.validator import ValidatorModule + self.validator_module = ValidatorModule(self.ton, self.local) + self.hostname = get_hostname() + self.set_global_vars() + self.inited = True + + def get_alert_from_db(self, alert_name: str): + if 'alerts' not in self.ton.local.db: + self.ton.local.db['alerts'] = {} + if alert_name not in self.ton.local.db['alerts']: + self.ton.local.db['alerts'][alert_name] = {'sent': 0, 'enabled': True} + return self.ton.local.db['alerts'][alert_name] + + def set_alert_sent(self, alert_name: str): + alert = self.get_alert_from_db(alert_name) + alert['sent'] = int(time.time()) + + def get_alert_sent(self, alert_name: str): + alert = self.get_alert_from_db(alert_name) + return alert.get('sent', 0) + + def alert_is_enabled(self, alert_name: str): + alert = self.get_alert_from_db(alert_name) + return alert.get('enabled', True) # default is True + + def set_alert_enabled(self, alert_name: str, enabled: bool): + alert = self.get_alert_from_db(alert_name) + alert['enabled'] = enabled + self.ton.local.save() + + def enable_alert(self, args): + if len(args) != 1: + raise Exception("Usage: enable_alert ") + alert_name = args[0] + self.set_alert_enabled(alert_name, True) + color_print("enable_alert - {green}OK{endc}") + + def disable_alert(self, args): + if len(args) != 1: + raise Exception("Usage: disable_alert ") + alert_name = args[0] + self.set_alert_enabled(alert_name, False) + color_print("disable_alert - {green}OK{endc}") + + def print_alerts(self, args): + table = [['Name', 'Enabled', 'Last sent']] + for alert_name in ALERTS: + alert = self.get_alert_from_db(alert_name) + table.append([alert_name, alert['enabled'], alert['sent']]) + print_table(table) + + def test_alert(self, args): + self.send_message('Test alert') + + def check_db_usage(self): + if time.time() - self.last_db_check < 600: + return + self.last_db_check = time.time() + usage = self.ton.GetDbUsage() + if usage > 95: + self.send_alert("db_usage_95") + elif usage > 80: + self.send_alert("db_usage_80") + + def check_validator_wallet_balance(self): + if not self.ton.using_validator(): + return + validator_wallet = self.ton.GetValidatorWallet() + validator_account = self.ton.GetAccount(validator_wallet.addrB64) + if validator_account.balance < 10: + self.send_alert("low_wallet_balance", wallet=validator_wallet.addrB64, balance=validator_account.balance) + + def check_efficiency(self): + if not self.ton.using_validator(): + return + validator = self.validator_module.find_myself(self.ton.GetValidatorsList()) + if validator is None or validator.efficiency is None: + return + config34 = self.ton.GetConfig34() + if (time.time() - config34.startWorkTime) / (config34.endWorkTime - config34.startWorkTime) < 0.8: + return # less than 80% of round passed + if validator.is_masterchain is False: + if validator.efficiency != 0: + return + if validator.efficiency < 90: + self.send_alert("low_efficiency", efficiency=validator.efficiency) + + def check_validator_working(self): + validator_status = self.ton.GetValidatorStatus() + if not validator_status.is_working: + self.send_alert("service_down") + + def check_sync(self): + validator_status = self.ton.GetValidatorStatus() + if validator_status.is_working and validator_status.out_of_sync >= 20: + self.send_alert("out_of_sync", sync=validator_status.out_of_sync) + + def check_zero_blocks_created(self): + if not self.ton.using_validator(): + return + ts = get_timestamp() + period = VALIDATION_PERIOD // 3 # 6h for mainnet, 40m for testnet + start, end = ts - period, ts - 60 + config34 = self.ton.GetConfig34() + if start < config34.startWorkTime: # round started recently + return + validators = self.ton.GetValidatorsList(start=start, end=end) + validator = self.validator_module.find_myself(validators) + if validator is None or validator.blocks_created > 0: + return + self.send_alert("zero_block_created", hours=round(period // 3600, 1)) + + def check_slashed(self): + if not self.ton.using_validator(): + return + c = self.validator_module.get_my_complaint() + if c is not None: + self.send_alert("validator_slashed", amount=int(c['suggestedFine'])) + + def check_adnl_connection_failed(self): + from modules.utilities import UtilitiesModule + utils_module = UtilitiesModule(self.ton, self.local) + ok, error = utils_module.check_adnl_connection() + if not ok: + self.send_alert("adnl_connection_failed") + + def check_status(self): + if not self.ton.using_alert_bot(): + return + if not self.inited: + self.init() + + self.local.try_function(self.check_db_usage) + self.local.try_function(self.check_validator_wallet_balance) + self.local.try_function(self.check_efficiency) # todo: alert if validator is going to be slashed + self.local.try_function(self.check_validator_working) + self.local.try_function(self.check_zero_blocks_created) + self.local.try_function(self.check_sync) + self.local.try_function(self.check_slashed) + self.local.try_function(self.check_adnl_connection_failed) + + def add_console_commands(self, console): + console.AddItem("enable_alert", self.enable_alert, self.local.translate("enable_alert_cmd")) + console.AddItem("disable_alert", self.disable_alert, self.local.translate("disable_alert_cmd")) + console.AddItem("list_alerts", self.print_alerts, self.local.translate("list_alerts_cmd")) + console.AddItem("test_alert", self.test_alert, self.local.translate("test_alert_cmd")) diff --git a/modules/utilities.py b/modules/utilities.py index 958a564a..da108152 100644 --- a/modules/utilities.py +++ b/modules/utilities.py @@ -1,9 +1,10 @@ -import base64 import json -import os +import random import subprocess import time +import requests + from mypylib.mypylib import color_print, print_table, color_text, timeago, bcolors from modules.module import MtcModule @@ -335,6 +336,50 @@ def print_validator_list(self, args): print_table(table) # end define + def check_adnl_connection(self): + telemetry = self.ton.local.db.get("sendTelemetry", False) + check_adnl = self.ton.local.db.get("checkAdnl", telemetry) + if not check_adnl: + return True, '' + self.local.add_log('Checking ADNL connection to local node', 'info') + hosts = ['45.129.96.53', '5.154.181.153', '2.56.126.137', '91.194.11.68', '45.12.134.214', '138.124.184.27', + '103.106.3.171'] + hosts = random.sample(hosts, k=3) + data = self.ton.get_local_adnl_data() + error = '' + ok = True + for host in hosts: + url = f'http://{host}/adnl_check' + try: + response = requests.post(url, json=data, timeout=5).json() + except Exception as e: + ok = False + error = f'{{red}}Failed to check ADNL connection to local node: {type(e)}: {e}{{endc}}' + continue + result = response.get("ok") + if result: + ok = True + break + if not result: + ok = False + error = f'{{red}}Failed to check ADNL connection to local node: {response.get("message")}{{endc}}' + return ok, error + + def get_pool_data(self, args): + try: + pool_name = args[0] + except: + color_print("{red}Bad args. Usage:{endc} get_pool_data ") + return + if self.ton.IsAddr(pool_name): + pool_addr = pool_name + else: + pool = self.ton.GetLocalPool(pool_name) + pool_addr = pool.addrB64 + pool_data = self.ton.GetPoolData(pool_addr) + print(json.dumps(pool_data, indent=4)) + # end define + def add_console_commands(self, console): console.AddItem("vas", self.view_account_status, self.local.translate("vas_cmd")) console.AddItem("vah", self.view_account_history, self.local.translate("vah_cmd")) @@ -350,3 +395,4 @@ def add_console_commands(self, console): console.AddItem("vl", self.print_validator_list, self.local.translate("vl_cmd")) console.AddItem("cl", self.print_complaints_list, self.local.translate("cl_cmd")) + console.AddItem("get_pool_data", self.get_pool_data, self.local.translate("get_pool_data_cmd")) diff --git a/modules/validator.py b/modules/validator.py index 7a6b9ad3..a0fdfa9c 100644 --- a/modules/validator.py +++ b/modules/validator.py @@ -54,15 +54,18 @@ def check_efficiency(self, args): end_time = timestamp2utcdatetime(config32.endWorkTime) color_print(f"Previous round time: {{yellow}}from {start_time} to {end_time}{{endc}}") if validator: - if validator.is_masterchain == False: - print("Validator index is greater than 100 in the previous round - no efficiency data.") - elif validator.get('efficiency') is None: + if validator.get('efficiency') is None: print('Failed to get efficiency for the previous round') + elif validator.is_masterchain is False and validator.get('efficiency') != 0: + print(f"Validator index is greater than {config32['mainValidators']} in the previous round - no efficiency data.") else: efficiency = 100 if validator.efficiency > 100 else validator.efficiency color_efficiency = GetColorInt(efficiency, 90, logic="more", ending="%") - created = validator.blocks_created - expected = validator.blocks_expected + created = validator.master_blocks_created + expected = validator.master_blocks_expected + if created is None: # there is no updated prev round info in cache + created = validator.blocks_created + expected = validator.blocks_expected color_print(f"Previous round efficiency: {color_efficiency} {{yellow}}({created} blocks created / {round(expected, 1)} blocks expected){{endc}}") else: print("Couldn't find this validator in the previous round") @@ -71,8 +74,8 @@ def check_efficiency(self, args): end_time = timestamp2utcdatetime(int(get_timestamp())) color_print(f"Current round time: {{green}}from {start_time} to {end_time}{{endc}}") if validator: - if validator.is_masterchain == False: - print("Validator index is greater than 100 in the current round - no efficiency data.") + if validator.is_masterchain is False and validator.efficiency != 0: + print(f"Validator index is greater than {config34['mainValidators']} in the current round - no efficiency data.") elif (time.time() - config34.startWorkTime) / (config34.endWorkTime - config34.startWorkTime) < 0.8: print("The validation round has started recently, there is not enough data yet. " "The efficiency evaluation will become more accurate towards the end of the round.") @@ -81,13 +84,24 @@ def check_efficiency(self, args): else: efficiency = 100 if validator.efficiency > 100 else validator.efficiency color_efficiency = GetColorInt(efficiency, 90, logic="more", ending="%") - created = validator.blocks_created - expected = validator.blocks_expected + created = validator.master_blocks_created + expected = validator.master_blocks_expected color_print(f"Current round efficiency: {color_efficiency} {{yellow}}({created} blocks created / {round(expected, 1)} blocks expected){{endc}}") else: print("Couldn't find this validator in the current round") # end define + def get_my_complaint(self): + config32 = self.ton.GetConfig32() + save_complaints = self.ton.GetSaveComplaints() + complaints = save_complaints.get(str(config32['startWorkTime'])) + if not complaints: + return + for c in complaints.values(): + if c["adnl"] == self.ton.GetAdnlAddr() and c["isPassed"]: + return c + # end define + def add_console_commands(self, console): console.AddItem("vo", self.vote_offer, self.local.translate("vo_cmd")) console.AddItem("ve", self.vote_election_entry, self.local.translate("ve_cmd")) diff --git a/mytoncore/functions.py b/mytoncore/functions.py index 686fb657..0dd136ff 100755 --- a/mytoncore/functions.py +++ b/mytoncore/functions.py @@ -569,6 +569,9 @@ def General(local): from modules.custom_overlays import CustomOverlayModule local.start_cycle(CustomOverlayModule(ton, local).custom_overlays, sec=60, args=()) + from modules.alert_bot import AlertBotModule + local.start_cycle(AlertBotModule(ton, local).check_status, sec=60, args=()) + thr_sleep() # end define diff --git a/mytoncore/mytoncore.py b/mytoncore/mytoncore.py index fe2ffb37..76cf58cd 100644 --- a/mytoncore/mytoncore.py +++ b/mytoncore/mytoncore.py @@ -1226,8 +1226,13 @@ def WaitTransaction(self, wallet, timeout=30): steps = timeout // timesleep for i in range(steps): time.sleep(timesleep) - seqno = self.GetSeqno(wallet) + try: + seqno = self.GetSeqno(wallet) + except: + self.local.add_log("WaitTransaction error: Can't get seqno", "warning") + continue if seqno != wallet.oldseqno: + self.local.add_log("WaitTransaction success", "info") return raise Exception("WaitTransaction error: time out") #end define @@ -1619,7 +1624,7 @@ def CreateWallet(self, name, workchain=0, version="v1", **kwargs): if os.path.isfile(wallet_path + ".pk") and "v3" not in version: self.local.add_log("CreateWallet error: Wallet already exists: " + name, "warning") else: - fift_args = self.get_new_wallet_fift_args(version, workchain=workchain, + fift_args = self.get_new_wallet_fift_args(version, workchain=workchain, wallet_path=wallet_path, subwallet=subwallet) result = self.fift.Run(fift_args) if "Creating new" not in result: @@ -1676,7 +1681,7 @@ def import_wallet_with_version(self, key, version, **kwargs): wallet_path = self.walletsDir + wallet_name with open(wallet_path + ".pk", 'wb') as file: file.write(pk_bytes) - fift_args = self.get_new_wallet_fift_args(version, workchain=workchain, + fift_args = self.get_new_wallet_fift_args(version, workchain=workchain, wallet_path=wallet_path, subwallet=subwallet) result = self.fift.Run(fift_args) if "Creating new" not in result: @@ -2305,6 +2310,7 @@ def get_valid_complaints(self, complaints: dict, election_id: int): continue exists = False + vload = None for item in validators_load.values(): if 'fileName' not in item: continue @@ -2314,15 +2320,16 @@ def get_valid_complaints(self, complaints: dict, election_id: int): pseudohash = pubkey + str(election_id) if pseudohash == complaint['pseudohash']: exists = True - vid = item['id'] + vload = item break if not exists: self.local.add_log(f"complaint {complaint['hash_hex']} declined: complaint info was not found, probably it's wrong", "info") continue - if vid >= config32['mainValidators']: - self.local.add_log(f"complaint {complaint['hash_hex']} declined: complaint created for non masterchain validator", "info") + if (vload["id"] >= config32['mainValidators'] and + vload["masterBlocksCreated"] + vload["workBlocksCreated"] > 0): + self.local.add_log(f"complaint {complaint['hash_hex']} declined: complaint created for non masterchain validator that created more than zero blocks", "info") continue # check complaint fine value @@ -2350,7 +2357,7 @@ def GetOnlineValidators(self): def GetValidatorsLoad(self, start, end, saveCompFiles=False) -> dict: # Get buffer - bname = f"validatorsLoad{start}{end}" + bname = f"validatorsLoad{start}{end}{saveCompFiles}" buff = self.GetFunctionBuffer(bname, timeout=60) if buff: return buff @@ -2394,7 +2401,10 @@ def GetValidatorsLoad(self, start, end, saveCompFiles=False) -> dict: wr = 0 else: wr = workBlocksCreated / workBlocksExpected - r = (mr + wr) / 2 + if masterBlocksExpected > 0: # show only masterchain efficiency for masterchain validator + r = mr + else: + r = (mr + wr) / 2 efficiency = round(r * 100, 2) if efficiency > 10: online = True @@ -2430,21 +2440,23 @@ def GetValidatorsLoad(self, start, end, saveCompFiles=False) -> dict: return data #end define - def GetValidatorsList(self, past=False, fast=False): + def GetValidatorsList(self, past=False, fast=False, start=None, end=None): # Get buffer - bname = "validatorsList" + str(past) + bname = "validatorsList" + str(past) + str(start) + str(end) buff = self.GetFunctionBuffer(bname, timeout=60) if buff: return buff #end if - timestamp = get_timestamp() - end = timestamp - 60 config = self.GetConfig34() - if fast: - start = end - 1000 - else: - start = config.get("startWorkTime") + if end is None: + timestamp = get_timestamp() + end = timestamp - 60 + if start is None: + if fast: + start = end - 1000 + else: + start = config.get("startWorkTime") if past: config = self.GetConfig32() start = config.get("startWorkTime") @@ -2467,6 +2479,8 @@ def GetValidatorsList(self, past=False, fast=False): validator["wr"] = validatorsLoad[vid]["wr"] validator["efficiency"] = validatorsLoad[vid]["efficiency"] validator["online"] = validatorsLoad[vid]["online"] + validator["master_blocks_created"] = validatorsLoad[vid]["masterBlocksCreated"] + validator["master_blocks_expected"] = validatorsLoad[vid]["masterBlocksExpected"] validator["blocks_created"] = validatorsLoad[vid]["masterBlocksCreated"] + validatorsLoad[vid]["workBlocksCreated"] validator["blocks_expected"] = validatorsLoad[vid]["masterBlocksExpected"] + validatorsLoad[vid]["workBlocksExpected"] validator["is_masterchain"] = False @@ -2514,7 +2528,7 @@ def CheckValidators(self, start, end): pseudohash = pubkey + str(electionId) if pseudohash in valid_complaints or pseudohash in voted_complaints_pseudohashes: # do not create complaints that already created or voted by ourself continue - if item['id'] >= config['mainValidators']: # do not create complaints for non-masterchain validators + if item['id'] >= config['mainValidators'] and item["masterBlocksCreated"] + item["workBlocksCreated"] > 0: # create complaints for non-masterchain validators only if they created 0 blocks continue # Create complaint fileName = self.remove_proofs_from_complaint(fileName) @@ -2600,24 +2614,6 @@ def GetDbSize(self, exceptions="log"): return result #end define - def check_adnl(self): - telemetry = self.local.db.get("sendTelemetry", False) - check_adnl = self.local.db.get("checkAdnl", telemetry) - if not check_adnl: - return - url = 'http://45.129.96.53/adnl_check' - try: - data = self.get_local_adnl_data() - response = requests.post(url, json=data, timeout=5).json() - except Exception as e: - self.local.add_log(f'Failed to check adnl connection: {type(e)}: {e}', 'error') - return False - result = response.get("ok") - if not result: - self.local.add_log(f'Failed to check adnl connection to local node: {response.get("message")}', 'error') - return result - #end define - def get_local_adnl_data(self): def int2ip(dec): @@ -3052,6 +3048,9 @@ def check_enable_mode(self, name): if self.using_liteserver(): raise Exception(f'Cannot enable validator mode while liteserver mode is enabled. ' f'Use `disable_mode liteserver` first.') + if name == 'liquid-staking': + from mytoninstaller.settings import enable_ton_http_api + enable_ton_http_api(self.local) def enable_mode(self, name): if name not in MODES: @@ -3092,6 +3091,9 @@ def using_validator(self): def using_liteserver(self): return self.get_mode_value('liteserver') + def using_alert_bot(self): + return self.get_mode_value('alert-bot') + def Tlb2Json(self, text): # Заменить скобки start = 0 @@ -3288,9 +3290,8 @@ def PendWithdrawFromPool(self, poolAddr, amount): #end define def HandlePendingWithdraw(self, pendingWithdraws, poolAddr): - amount = pendingWithdraws.get(poolAddr) + amount = pendingWithdraws.pop(poolAddr) self.WithdrawFromPoolProcess(poolAddr, amount) - pendingWithdraws.pop(poolAddr) #end define def GetPendingWithdraws(self): @@ -3604,7 +3605,7 @@ def CalculateLoanAmount(self, min_loan, max_loan, max_interest): print(f"CalculateLoanAmount data: {data}") url = "http://127.0.0.1:8801/runGetMethod" - res = requests.post(url, json=data) + res = requests.post(url, json=data, timeout=3) res_data = res.json() if res_data.get("ok") is False: error = res_data.get("error") diff --git a/mytoncore/utils.py b/mytoncore/utils.py index 0a8bdc91..a31e299c 100644 --- a/mytoncore/utils.py +++ b/mytoncore/utils.py @@ -1,6 +1,7 @@ import base64 import json import re +import subprocess def str2b64(s): @@ -97,3 +98,6 @@ def parse_db_stats(path: str): result[s[0]] = {k: float(v) for k, v in items} return result # end define + +def get_hostname(): + return subprocess.run(["hostname", "-f"], stdout=subprocess.PIPE).stdout.decode().strip() diff --git a/mytonctrl/mytonctrl.py b/mytonctrl/mytonctrl.py index ba606750..4b3dd433 100755 --- a/mytonctrl/mytonctrl.py +++ b/mytonctrl/mytonctrl.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf_8 -*- import base64 +import random import subprocess import json import psutil @@ -10,6 +11,8 @@ from functools import partial +import requests + from mypylib.mypylib import ( int2ip, get_git_author_and_repo, @@ -29,7 +32,7 @@ color_text, bcolors, Dict, - MyPyClass + MyPyClass, ip2int ) from mypyconsole.mypyconsole import MyPyConsole @@ -46,6 +49,8 @@ import sys, getopt, os +from mytoninstaller.config import get_own_ip + def Init(local, ton, console, argv): # Load translate table @@ -80,6 +85,8 @@ def inject_globals(func): console.AddItem("get", inject_globals(GetSettings), local.translate("get_cmd")) console.AddItem("set", inject_globals(SetSettings), local.translate("set_cmd")) console.AddItem("rollback", inject_globals(rollback_to_mtc1), local.translate("rollback_cmd")) + console.AddItem("create_backup", inject_globals(create_backup), local.translate("create_backup_cmd")) + console.AddItem("restore_backup", inject_globals(restore_backup), local.translate("restore_backup_cmd")) #console.AddItem("xrestart", inject_globals(Xrestart), local.translate("xrestart_cmd")) #console.AddItem("xlist", inject_globals(Xlist), local.translate("xlist_cmd")) @@ -128,6 +135,11 @@ def inject_globals(func): module = ControllerModule(ton, local) module.add_console_commands(console) + if ton.using_alert_bot(): + from modules.alert_bot import AlertBotModule + module = AlertBotModule(ton, local) + module.add_console_commands(console) + console.AddItem("cleanup", inject_globals(cleanup_validator_db), local.translate("cleanup_cmd")) console.AddItem("benchmark", inject_globals(run_benchmark), local.translate("benchmark_cmd")) # console.AddItem("activate_ton_storage_provider", inject_globals(activate_ton_storage_provider), local.translate("activate_ton_storage_provider_cmd")) @@ -218,7 +230,6 @@ def PreUp(local: MyPyClass, ton: MyTonCore): CheckMytonctrlUpdate(local) check_installer_user(local) check_vport(local, ton) - ton.check_adnl() warnings(local, ton) # CheckTonUpdate() #end define @@ -474,19 +485,29 @@ def check_tg_channel(local, ton): #end difine def check_slashed(local, ton): - config32 = ton.GetConfig32() - save_complaints = ton.GetSaveComplaints() - complaints = save_complaints.get(str(config32['startWorkTime'])) - if not complaints: + validator_status = ton.GetValidatorStatus() + if not ton.using_validator() or not validator_status.is_working or validator_status.out_of_sync >= 20: return - for c in complaints.values(): - if c["adnl"] == ton.GetAdnlAddr() and c["isPassed"]: - print_warning(local, "slashed_warning") + from modules import ValidatorModule + validator_module = ValidatorModule(ton, local) + c = validator_module.get_my_complaint() + if c: + warning = local.translate("slashed_warning").format(int(c['suggestedFine'])) + print_warning(local, warning) +#end define + +def check_adnl(local, ton): + from modules.utilities import UtilitiesModule + utils_module = UtilitiesModule(ton, local) + ok, error = utils_module.check_adnl_connection() + if not ok: + print_warning(local, error) #end define def warnings(local, ton): local.try_function(check_disk_usage, args=[local, ton]) local.try_function(check_sync, args=[local, ton]) + local.try_function(check_adnl, args=[local, ton]) local.try_function(check_validator_balance, args=[local, ton]) local.try_function(check_vps, args=[local, ton]) local.try_function(check_tg_channel, args=[local, ton]) @@ -506,6 +527,9 @@ def mode_status(ton, args): table = [["Name", "Status", "Description"]] for mode_name in modes: mode = get_mode(mode_name) + if mode is None: + color_print(f"{{red}}Mode {mode_name} not found{{endc}}") + continue status = color_text('{green}enabled{endc}' if modes[mode_name] else '{red}disabled{endc}') table.append([mode_name, status, mode.description]) print_table(table) @@ -914,6 +938,54 @@ def disable_mode(local, ton, args): local.exit() #end define + +def create_backup(local, ton, args): + if len(args) > 2: + color_print("{red}Bad args. Usage:{endc} create_backup [path_to_archive] [-y]") + return + if '-y' not in args: + res = input(f'Node and Mytoncore services will be stopped for few seconds while backup is created, Proceed [y/n]?') + if res.lower() != 'y': + print('aborted.') + return + else: + args.pop(args.index('-y')) + command_args = ["-m", ton.local.buffer.my_work_dir] + if len(args) == 1: + command_args += ["-d", args[0]] + backup_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/create_backup.sh') + if run_as_root(["bash", backup_script_path] + command_args) == 0: + color_print("create_backup - {green}OK{endc}") + else: + color_print("create_backup - {red}Error{endc}") +#end define + + +def restore_backup(local, ton, args): + if len(args) == 0 or len(args) > 2: + color_print("{red}Bad args. Usage:{endc} restore_backup [-y]") + return + if '-y' not in args: + res = input(f'This action will overwrite existing configuration with contents of backup archive, please make sure that donor node is not in operation prior to this action. Proceed [y/n]') + if res.lower() != 'y': + print('aborted.') + return + else: + args.pop(args.index('-y')) + print('Before proceeding, mtc will create a backup of current configuration.') + create_backup(local, ton, ['-y']) + ip = str(ip2int(get_own_ip())) + command_args = ["-m", ton.local.buffer.my_work_dir, "-n", args[0], "-i", ip] + + restore_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/restore_backup.sh') + if run_as_root(["bash", restore_script_path] + command_args) == 0: + color_print("restore_backup - {green}OK{endc}") + local.exit() + else: + color_print("restore_backup - {red}Error{endc}") +#end define + + def Xrestart(inputArgs): if len(inputArgs) < 2: color_print("{red}Bad args. Usage:{endc} xrestart ") diff --git a/mytonctrl/resources/translate.json b/mytonctrl/resources/translate.json index 632158b4..22915e59 100644 --- a/mytonctrl/resources/translate.json +++ b/mytonctrl/resources/translate.json @@ -445,9 +445,9 @@ "zh_TW": "{red}錯誤 - 驗證器的 UDP 端口無法從外部訪問.{endc}" }, "slashed_warning": { - "en": "{red}You were fined by 101 TON for low efficiency in the previous round.{endc}", - "ru": "{red}Вы были оштрафованы на 101 TON за низкую эффективность в предыдущем раунде.{endc}", - "zh_TW": "{red}您因上一輪效率低而被罰款 101 TON。{endc}" + "en": "{{red}}You were fined by {0} TON for low efficiency in the previous round.{{endc}}", + "ru": "{{red}}Вы были оштрафованы на {0} TON за низкую эффективность в предыдущем раунде.{{endc}}", + "zh_TW": "{{red}}您因上一輪效率低而被罰款 {0} TON。{{endc}}" }, "add_custom_overlay_cmd": { "en": "Add custom overlay", @@ -464,6 +464,26 @@ "ru": "Удалить пользовательский оверлей", "zh_TW": "刪除自定義覆蓋" }, + "enable_alert_cmd": { + "en": "Enable specific Telegram Bot alert", + "ru": "Включить определенное оповещение через Telegram Bot", + "zh_TW": "啟用特定的 Telegram Bot 警報" + }, + "disable_alert_cmd": { + "en": "Disable specific Telegram Bot alert", + "ru": "Отключить определенное оповещение через Telegram Bot", + "zh_TW": "禁用特定的 Telegram Bot 警報" + }, + "list_alerts_cmd": { + "en": "List all available Telegram Bot alerts", + "ru": "Список всех доступных оповещений через Telegram Bot", + "zh_TW": "列出所有可用的 Telegram Bot 警報" + }, + "test_alert_cmd": { + "en": "Send test alert via Telegram Bot", + "ru": "Отправить тестовое оповещение через Telegram Bot", + "zh_TW": "通過 Telegram Bot 發送測試警報" + }, "cleanup_cmd": { "en": "Clean node old logs and temp files", "ru": "Очистить старые логи и временные файлы ноды", diff --git a/mytonctrl/scripts/create_backup.sh b/mytonctrl/scripts/create_backup.sh new file mode 100644 index 00000000..9bab1355 --- /dev/null +++ b/mytonctrl/scripts/create_backup.sh @@ -0,0 +1,47 @@ +dest="mytonctrl_backup_$(hostname)_$(date +%s).tar.gz" +mtc_dir="$HOME/.local/share/mytoncore" +user=$(logname) +# Get arguments +while getopts d:m: flag +do + case "${flag}" in + d) dest=${OPTARG};; + m) mtc_dir=${OPTARG};; + *) + echo "Flag -${flag} is not recognized. Aborting" + exit 1 ;; + esac +done + +COLOR='\033[92m' +ENDC='\033[0m' + +systemctl stop validator +systemctl stop mytoncore + +echo -e "${COLOR}[1/4]${ENDC} Stopped validator and mytoncore" + + +tmp_dir="/tmp/mytoncore/backup" +rm -rf $tmp_dir +mkdir $tmp_dir + +cp /var/ton-work/db/config.json ${tmp_dir} +cp -r /var/ton-work/db/keyring ${tmp_dir} +cp -r /var/ton-work/keys ${tmp_dir} +cp -r $mtc_dir $tmp_dir + +echo -e "${COLOR}[2/4]${ENDC} Copied files to ${tmp_dir}" + + +systemctl start validator +systemctl start mytoncore + +echo -e "${COLOR}[3/4]${ENDC} Started validator and mytoncore" + +tar -zcf $dest -C $tmp_dir . + +chown $user:$user $dest + +echo -e "${COLOR}[4/4]${ENDC} Backup successfully created in ${dest}!" +echo -e "If you wish to use archive package to migrate node to different machine please make sure to stop validator and mytoncore on donor (this) host prior to migration." diff --git a/mytonctrl/scripts/restore_backup.sh b/mytonctrl/scripts/restore_backup.sh new file mode 100644 index 00000000..361c7ff9 --- /dev/null +++ b/mytonctrl/scripts/restore_backup.sh @@ -0,0 +1,51 @@ +name="backup.tar.gz" +mtc_dir="$HOME/.local/share/mytoncore" +ip=0 +# Get arguments +while getopts n:m:i: flag +do + case "${flag}" in + n) name=${OPTARG};; + m) mtc_dir=${OPTARG};; + i) ip=${OPTARG};; + *) + echo "Flag -${flag} is not recognized. Aborting" + exit 1 ;; + esac +done + + +COLOR='\033[92m' +ENDC='\033[0m' + +systemctl stop validator +systemctl stop mytoncore + +echo -e "${COLOR}[1/4]${ENDC} Stopped validator and mytoncore" + + +tmp_dir="/tmp/mytoncore/backup" +rm -rf $tmp_dir +mkdir $tmp_dir +tar -xvzf $name -C $tmp_dir + +rm -rf /var/ton-work/db/keyring +cp -f ${tmp_dir}/config.json /var/ton-work/db/ +cp -rf ${tmp_dir}/keyring /var/ton-work/db/ +cp -rf ${tmp_dir}/keys /var/ton-work +cp -rfT ${tmp_dir}/mytoncore $mtc_dir + +chown -R validator:validator /var/ton-work/db/keyring + +echo -e "${COLOR}[2/4]${ENDC} Extracted files from archive" + +rm -r /var/ton-work/db/dht-* + +python3 -c "import json;path='/var/ton-work/db/config.json';f=open(path);d=json.load(f);f.close();d['addrs'][0]['ip']=int($ip);f=open(path, 'w');f.write(json.dumps(d, indent=4));f.close()" + +echo -e "${COLOR}[3/4]${ENDC} Deleted DHT files, replaced IP in node config" + +systemctl start validator +systemctl start mytoncore + +echo -e "${COLOR}[4/4]${ENDC} Started validator and mytoncore" diff --git a/mytoninstaller/mytoninstaller.py b/mytoninstaller/mytoninstaller.py index c63111db..c5e60012 100644 --- a/mytoninstaller/mytoninstaller.py +++ b/mytoninstaller/mytoninstaller.py @@ -24,7 +24,7 @@ EnableLiteServer, EnableDhtServer, EnableJsonRpc, - EnableTonHttpApi, + enable_ton_http_api, DangerousRecoveryValidatorConfigFile, CreateSymlinks, enable_ls_proxy, @@ -223,7 +223,7 @@ def Event(local, name): if name == "enableJR": EnableJsonRpc(local) if name == "enableTHA": - EnableTonHttpApi(local) + enable_ton_http_api(local) if name == "enableLSP": enable_ls_proxy(local) if name == "enableTSP": diff --git a/scripts/ton_http_api_installer.sh b/mytoninstaller/scripts/ton_http_api_installer.sh similarity index 62% rename from scripts/ton_http_api_installer.sh rename to mytoninstaller/scripts/ton_http_api_installer.sh index 9327f870..fa87c42c 100644 --- a/scripts/ton_http_api_installer.sh +++ b/mytoninstaller/scripts/ton_http_api_installer.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -# Проверить sudo +# check sudo if [ "$(id -u)" != "0" ]; then echo "Please run script as root" exit 1 @@ -11,30 +11,36 @@ fi COLOR='\033[92m' ENDC='\033[0m' -# Установка компонентов python3 +# install python3 packages pip3 install virtualenv -# Подготовить папку с виртуальным окружением +# prepare the virtual environment echo -e "${COLOR}[1/4]${ENDC} Preparing the virtual environment" venv_path="/opt/virtualenv/ton_http_api" virtualenv ${venv_path} -# Установка компонентов python3 +# install python3 packages echo -e "${COLOR}[2/4]${ENDC} Installing required packages" user=$(logname) venv_pip3="${venv_path}/bin/pip3" ${venv_pip3} install ton-http-api chown -R ${user}:${user} ${venv_path} -# Прописать автозагрузку +# add to startup echo -e "${COLOR}[3/4]${ENDC} Add to startup" venv_ton_http_api="${venv_path}/bin/ton-http-api" tonlib_path="/usr/bin/ton/tonlib/libtonlibjson.so" -ls_config="/usr/bin/ton/localhost.config.json" -cmd="from sys import path; path.append('/usr/src/mytonctrl/'); from mypylib.mypylib import add2systemd; add2systemd(name='ton_http_api', user='${user}', start='${venv_ton_http_api} --host 127.0.0.1 --port 8801 --liteserver-config ${ls_config} --cdll-path ${tonlib_path} --tonlib-keystore /tmp/tonlib_keystore/')" +ls_config="/usr/bin/ton/local.config.json" +cmd="from sys import path; path.append('/usr/src/mytonctrl/'); from mypylib.mypylib import add2systemd; add2systemd(name='ton_http_api', user='${user}', start='${venv_ton_http_api} --logs-level=INFO --host 127.0.0.1 --port 8801 --liteserver-config ${ls_config} --cdll-path ${tonlib_path} --tonlib-keystore /tmp/tonlib_keystore/')" python3 -c "${cmd}" +systemctl daemon-reload systemctl restart ton_http_api -# Конец +# check connection +echo -e "Requesting masterchain info from local ton http api" +sleep 5 +curl http://127.0.0.1:8801/getMasterchainInfo + +# end echo -e "${COLOR}[4/4]${ENDC} ton_http_api service installation complete" exit 0 diff --git a/mytoninstaller/scripts/tonhttpapiinstaller.sh b/mytoninstaller/scripts/tonhttpapiinstaller.sh deleted file mode 100755 index c22b98e0..00000000 --- a/mytoninstaller/scripts/tonhttpapiinstaller.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -set -e - -# Проверить sudo -if [ "$(id -u)" != "0" ]; then - echo "Please run script as root" - exit 1 -fi - -# Get arguments -while getopts u: flag -do - case "${flag}" in - u) user=${OPTARG};; - esac -done - -# Цвета -COLOR='\033[92m' -ENDC='\033[0m' - -# Установка компонентов python3 -echo -e "${COLOR}[1/3]${ENDC} Installing required packages" -pip3 install -U ton-http-api - -# Установка модуля -echo -e "${COLOR}[2/3]${ENDC} Add to startup" -mkdir -p /var/ton-http-api/ton_keystore/ -chown -R $user /var/ton-http-api/ - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -cmd="ton-http-api --port=8000 --logs-level=INFO --cdll-path=/usr/bin/ton/tonlib/libtonlibjson.so --liteserver-config /usr/bin/ton/local.config.json --tonlib-keystore=/var/ton-http-api/ton_keystore/ --parallel-requests-per-liteserver=1024" -${SCRIPT_DIR}/add2systemd.sh -n ton-http-api -s "${cmd}" -u ${user} -g ${user} -systemctl restart ton-http-api - -# Конец -echo -e "${COLOR}[3/3]${ENDC} TonHttpApi installation complete" -exit 0 diff --git a/mytoninstaller/settings.py b/mytoninstaller/settings.py index 45d167bb..76e92908 100644 --- a/mytoninstaller/settings.py +++ b/mytoninstaller/settings.py @@ -9,10 +9,10 @@ import pkg_resources from mypylib.mypylib import ( - add2systemd, - get_dir_from_path, - run_as_root, - color_print, + add2systemd, + get_dir_from_path, + run_as_root, + color_print, ip2int, Dict ) @@ -88,33 +88,42 @@ def FirstNodeSettings(local): StartValidator(local) #end define - def DownloadDump(local): - dump = local.buffer.dump - if dump == False: - return - #end if - - local.add_log("start DownloadDump fuction", "debug") - url = "https://dump.ton.org" - dumpSize = requests.get(url + "/dumps/latest.tar.size.archive.txt").text - print("dumpSize:", dumpSize) - needSpace = int(dumpSize) * 3 - diskSpace = psutil.disk_usage("/var") - if needSpace > diskSpace.free: - return - #end if - - # apt install - cmd = "apt install plzip pv curl -y" - os.system(cmd) - - # download dump - cmd = "curl -s {url}/dumps/latest.tar.lz | pv | plzip -d -n8 | tar -xC /var/ton-work/db".format(url=url) - os.system(cmd) + dump = local.buffer.dump + if dump == False: + return + #end if + + local.add_log("start DownloadDump function", "debug") + url = "https://dump.ton.org" + dumpSize = requests.get(url + "/dumps/latest.tar.size.archive.txt").text + print("dumpSize:", dumpSize) + needSpace = int(dumpSize) * 3 + diskSpace = psutil.disk_usage("/var") + if needSpace > diskSpace.free: + return + #end if + + # apt install + cmd = "apt install plzip pv aria2 curl -y" + os.system(cmd) + + # download dump using aria2c to a temporary file + temp_file = "/tmp/latest.tar.lz" + cmd = f"aria2c -x 8 -s 8 -c {url}/dumps/latest.tar.lz -d / -o {temp_file}" + os.system(cmd) + + # process the downloaded file + cmd = f"pv {temp_file} | plzip -d -n8 | tar -xC /var/ton-work/db" + os.system(cmd) + + # clean up the temporary file after processing + if os.path.exists(temp_file): + os.remove(temp_file) + local.add_log(f"Temporary file {temp_file} removed", "debug") + #end if #end define - def FirstMytoncoreSettings(local): local.add_log("start FirstMytoncoreSettings fuction", "debug") user = local.buffer.user @@ -451,12 +460,10 @@ def EnableJsonRpc(local): color_print(text) #end define -def EnableTonHttpApi(local): - local.add_log("start EnablePytonv3 function", "debug") - user = local.buffer.user - - ton_http_api_installer_path = pkg_resources.resource_filename('mytoninstaller.scripts', 'tonhttpapiinstaller.sh') - exit_code = run_as_root(["bash", ton_http_api_installer_path, "-u", user]) +def enable_ton_http_api(local): + local.add_log("start EnableTonHttpApi function", "debug") + ton_http_api_installer_path = pkg_resources.resource_filename('mytoninstaller.scripts', 'ton_http_api_installer.sh') + exit_code = run_as_root(["bash", ton_http_api_installer_path]) if exit_code == 0: text = "EnableTonHttpApi - {green}OK{endc}" else: diff --git a/scripts/install.sh b/scripts/install.sh index fe5ac3d6..dffe1134 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -20,19 +20,20 @@ ton_node_version="master" # Default version show_help_and_exit() { - echo 'Supported arguments:' - echo ' -c PATH Provide custom config for toninstaller.sh' - echo ' -t Disable telemetry' - echo ' -i Ignore minimum requirements' - echo ' -d Use pre-packaged dump. Reduces duration of initial synchronization.' - echo ' -a Set MyTonCtrl git repo author' - echo ' -r Set MyTonCtrl git repo' - echo ' -b Set MyTonCtrl git repo branch' - echo ' -m MODE Install MyTonCtrl with specified mode (validator or liteserver)' - echo ' -n NETWORK Specify the network (mainnet or testnet)' - echo ' -v VERSION Specify the ton node version (commit, branch, or tag)' - echo ' -h Show this help' - exit + echo 'Supported arguments:' + echo ' -c PATH Provide custom config for toninstaller.sh' + echo ' -t Disable telemetry' + echo ' -i Ignore minimum requirements' + echo ' -d Use pre-packaged dump. Reduces duration of initial synchronization.' + echo ' -a Set MyTonCtrl git repo author' + echo ' -r Set MyTonCtrl git repo' + echo ' -b Set MyTonCtrl git repo branch' + echo ' -m MODE Install MyTonCtrl with specified mode (validator or liteserver)' + echo ' -n NETWORK Specify the network (mainnet or testnet)' + echo ' -v VERSION Specify the ton node version (commit, branch, or tag)' + echo ' -u USER Specify the user to be used for MyTonCtrl installation' + echo ' -h Show this help' + exit } if [[ "${1-}" =~ ^-*h(elp)?$ ]]; then @@ -47,7 +48,7 @@ dump=false cpu_required=16 mem_required=64000000 # 64GB in KB -while getopts ":c:tida:r:b:m:n:v:h" flag; do +while getopts ":c:tida:r:b:m:n:v:u:h" flag; do case "${flag}" in c) config=${OPTARG};; t) telemetry=false;; @@ -59,10 +60,11 @@ while getopts ":c:tida:r:b:m:n:v:h" flag; do m) mode=${OPTARG};; n) network=${OPTARG};; v) ton_node_version=${OPTARG};; + u) user=${OPTARG};; h) show_help_and_exit;; *) echo "Flag -${flag} is not recognized. Aborting" - exit 1 ;; + exit 1 ;; esac done @@ -90,8 +92,8 @@ memory=$(cat /proc/meminfo | grep MemTotal | awk '{print $2}') echo "This machine has ${cpus} CPUs and ${memory}KB of Memory" if [ "$ignore" = false ] && ([ "${cpus}" -lt "${cpu_required}" ] || [ "${memory}" -lt "${mem_required}" ]); then - echo "Insufficient resources. Requires a minimum of "${cpu_required}" processors and "${mem_required}" RAM." - exit 1 + echo "Insufficient resources. Requires a minimum of "${cpu_required}" processors and "${mem_required}" RAM." + exit 1 fi echo -e "${COLOR}[2/5]${ENDC} Checking for required TON components" @@ -100,9 +102,9 @@ BIN_DIR=/usr/bin # create dirs for OSX if [[ "$OSTYPE" =~ darwin.* ]]; then - SOURCES_DIR=/usr/local/src - BIN_DIR=/usr/local/bin - mkdir -p ${SOURCES_DIR} + SOURCES_DIR=/usr/local/src + BIN_DIR=/usr/local/bin + mkdir -p ${SOURCES_DIR} fi # check TON components @@ -111,9 +113,9 @@ file2=${BIN_DIR}/ton/lite-client/lite-client file3=${BIN_DIR}/ton/validator-engine-console/validator-engine-console if [ ! -f "${file1}" ] || [ ! -f "${file2}" ] || [ ! -f "${file3}" ]; then - echo "TON does not exists, building" - wget https://raw.githubusercontent.com/${author}/${repo}/${branch}/scripts/ton_installer.sh -O /tmp/ton_installer.sh - bash /tmp/ton_installer.sh -c ${config} -v ${ton_node_version} + echo "TON does not exists, building" + wget https://raw.githubusercontent.com/${author}/${repo}/${branch}/scripts/ton_installer.sh -O /tmp/ton_installer.sh + bash /tmp/ton_installer.sh -c ${config} -v ${ton_node_version} fi # Cloning mytonctrl @@ -134,10 +136,12 @@ pip3 install -U . # TODO: make installation from git directly echo -e "${COLOR}[4/5]${ENDC} Running mytoninstaller" # DEBUG -parent_name=$(ps -p $PPID -o comm=) -user=$(whoami) -if [ "$parent_name" = "sudo" ] || [ "$parent_name" = "su" ] || [ "$parent_name" = "python3" ]; then - user=$(logname) +if [ "${user}" = "" ]; then # no user + parent_name=$(ps -p $PPID -o comm=) + user=$(whoami) + if [ "$parent_name" = "sudo" ] || [ "$parent_name" = "su" ] || [ "$parent_name" = "python3" ]; then + user=$(logname) + fi fi echo "User: $user" python3 -m mytoninstaller -u ${user} -t ${telemetry} --dump ${dump} -m ${mode}