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}