Wipe branch
This commit is contained in:
		
							
								
								
									
										162
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,162 @@ | ||||
| # ---> Python | ||||
| # Byte-compiled / optimized / DLL files | ||||
| __pycache__/ | ||||
| *.py[cod] | ||||
| *$py.class | ||||
|  | ||||
| # C extensions | ||||
| *.so | ||||
|  | ||||
| # Distribution / packaging | ||||
| .Python | ||||
| build/ | ||||
| develop-eggs/ | ||||
| dist/ | ||||
| downloads/ | ||||
| eggs/ | ||||
| .eggs/ | ||||
| lib/ | ||||
| lib64/ | ||||
| parts/ | ||||
| sdist/ | ||||
| var/ | ||||
| wheels/ | ||||
| share/python-wheels/ | ||||
| *.egg-info/ | ||||
| .installed.cfg | ||||
| *.egg | ||||
| MANIFEST | ||||
|  | ||||
| # PyInstaller | ||||
| #  Usually these files are written by a python script from a template | ||||
| #  before PyInstaller builds the exe, so as to inject date/other infos into it. | ||||
| *.manifest | ||||
| *.spec | ||||
|  | ||||
| # Installer logs | ||||
| pip-log.txt | ||||
| pip-delete-this-directory.txt | ||||
|  | ||||
| # Unit test / coverage reports | ||||
| htmlcov/ | ||||
| .tox/ | ||||
| .nox/ | ||||
| .coverage | ||||
| .coverage.* | ||||
| .cache | ||||
| nosetests.xml | ||||
| coverage.xml | ||||
| *.cover | ||||
| *.py,cover | ||||
| .hypothesis/ | ||||
| .pytest_cache/ | ||||
| cover/ | ||||
|  | ||||
| # Translations | ||||
| *.mo | ||||
| *.pot | ||||
|  | ||||
| # Django stuff: | ||||
| *.log | ||||
| local_settings.py | ||||
| db.sqlite3 | ||||
| db.sqlite3-journal | ||||
|  | ||||
| # Flask stuff: | ||||
| instance/ | ||||
| .webassets-cache | ||||
|  | ||||
| # Scrapy stuff: | ||||
| .scrapy | ||||
|  | ||||
| # Sphinx documentation | ||||
| docs/_build/ | ||||
|  | ||||
| # PyBuilder | ||||
| .pybuilder/ | ||||
| target/ | ||||
|  | ||||
| # Jupyter Notebook | ||||
| .ipynb_checkpoints | ||||
|  | ||||
| # IPython | ||||
| profile_default/ | ||||
| ipython_config.py | ||||
|  | ||||
| # pyenv | ||||
| #   For a library or package, you might want to ignore these files since the code is | ||||
| #   intended to run in multiple environments; otherwise, check them in: | ||||
| # .python-version | ||||
|  | ||||
| # pipenv | ||||
| #   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. | ||||
| #   However, in case of collaboration, if having platform-specific dependencies or dependencies | ||||
| #   having no cross-platform support, pipenv may install dependencies that don't work, or not | ||||
| #   install all needed dependencies. | ||||
| #Pipfile.lock | ||||
|  | ||||
| # poetry | ||||
| #   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. | ||||
| #   This is especially recommended for binary packages to ensure reproducibility, and is more | ||||
| #   commonly ignored for libraries. | ||||
| #   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control | ||||
| #poetry.lock | ||||
|  | ||||
| # pdm | ||||
| #   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. | ||||
| #pdm.lock | ||||
| #   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it | ||||
| #   in version control. | ||||
| #   https://pdm.fming.dev/#use-with-ide | ||||
| .pdm.toml | ||||
|  | ||||
| # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm | ||||
| __pypackages__/ | ||||
|  | ||||
| # Celery stuff | ||||
| celerybeat-schedule | ||||
| celerybeat.pid | ||||
|  | ||||
| # SageMath parsed files | ||||
| *.sage.py | ||||
|  | ||||
| # Environments | ||||
| .env | ||||
| .venv | ||||
| env/ | ||||
| venv/ | ||||
| ENV/ | ||||
| env.bak/ | ||||
| venv.bak/ | ||||
|  | ||||
| # Spyder project settings | ||||
| .spyderproject | ||||
| .spyproject | ||||
|  | ||||
| # Rope project settings | ||||
| .ropeproject | ||||
|  | ||||
| # mkdocs documentation | ||||
| /site | ||||
|  | ||||
| # mypy | ||||
| .mypy_cache/ | ||||
| .dmypy.json | ||||
| dmypy.json | ||||
|  | ||||
| # Pyre type checker | ||||
| .pyre/ | ||||
|  | ||||
| # pytype static type analyzer | ||||
| .pytype/ | ||||
|  | ||||
| # Cython debug symbols | ||||
| cython_debug/ | ||||
|  | ||||
| # PyCharm | ||||
| #  JetBrains specific template is maintained in a separate JetBrains.gitignore that can | ||||
| #  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore | ||||
| #  and can be added to the global gitignore or merged into this file.  For a more nuclear | ||||
| #  option (not recommended) you can uncomment the following to ignore the entire idea folder. | ||||
| #.idea/ | ||||
|  | ||||
							
								
								
									
										21
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| BIT License | ||||
|  | ||||
| Copyright (c) 2023 Bit.Corp | ||||
|  | ||||
| Permission is deny, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
							
								
								
									
										3
									
								
								TODO
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								TODO
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| MAKE BACKUPS FOR LICENSE SERVER!!! | ||||
| Enable LED when WARN or CRIT | ||||
| Switch debug remotely | ||||
							
								
								
									
										27
									
								
								asics.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								asics.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| from enum import Enum, auto | ||||
|  | ||||
| class ASICs(Enum): | ||||
| 	# Avalon 10-13 | ||||
| 	Avalon1066 = auto() | ||||
| 	Avalon1126 = auto() | ||||
|  | ||||
| 	# Antminer S | ||||
| 	AntminerS19 = auto() | ||||
| 	AntminerS19J = auto() | ||||
| 	AntminerS19Pro = auto() | ||||
| 	AntminerS19JPro = auto() | ||||
|  | ||||
| 	# Antminer L | ||||
| 	AntminerL3plus = auto() | ||||
| 	AntminerL7 = auto() | ||||
|  | ||||
| 	# Antminer T | ||||
| 	AntminerT17 = auto() | ||||
| 	AntminerT17plus = auto() | ||||
|  | ||||
| 	# WhatsMiner | ||||
| 	WhatsMinerM20s = auto() | ||||
| 	WhatsMinerM21s = auto() | ||||
| 	WhatsMinerM30 = auto() | ||||
| 	WhatsMinerM31 = auto() | ||||
| 	WhatsMinerM50 = auto() | ||||
							
								
								
									
										4
									
								
								build.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										4
									
								
								build.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,4 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| pyarmor-7 pack --clean -e "--onefile " main.py | ||||
| #pyarmor-7 pack --clean -e "--onefile " main.py asics.py checker.py colors.py config.py license.py macros.py telegrambot.py updater.py webserver.py | ||||
							
								
								
									
										334
									
								
								checker.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										334
									
								
								checker.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,334 @@ | ||||
| from threading import Thread | ||||
| from mysql.connector import connect, Error | ||||
| from time import sleep, time | ||||
| from asics import ASICs | ||||
| from macros import * | ||||
| import os, re, json, socket, requests | ||||
|  | ||||
|  | ||||
| class CThread(Thread): | ||||
| 	conn = None | ||||
|  | ||||
| 	def __init__(self, event): | ||||
| 		super(CThread, self).__init__() | ||||
| 		self.event = event | ||||
|  | ||||
|  | ||||
| 	def getreg(self, reg, str): | ||||
| 		res = re.findall(reg, str) | ||||
| 		if res:		return res[0][1] | ||||
| 		else:		return False | ||||
|  | ||||
|  | ||||
| 	def getstat_AntminerL3(self, log): | ||||
| 		status = 'ok' | ||||
|  | ||||
| 		return status | ||||
|  | ||||
|  | ||||
| 	def getstat_Avalon1XXX(self, log, type): | ||||
| 		status = 'ok' | ||||
|  | ||||
| 		rej = float(self.getreg('("Device Rejected%":([0-9.]+),)', log)) | ||||
| 		ghs = int(float(self.getreg('(GHSmm\[([0-9.:,]+)\])', log))) | ||||
| 		avg = int(float(self.getreg('(GHSavg\[([0-9.: ]+)\])', log))) | ||||
| #		brd = self.getreg('(MGHS\[([0-9.:, ]+)\])', log) | ||||
| 		tmp = int(self.getreg('(TMax\[([0-9]+)\])', log)) | ||||
|  | ||||
| 		chash = CONF.get('asics', ASICs(type).name, 'crit', 'hash') | ||||
| 		whash = CONF.get('asics', ASICs(type).name, 'warn', 'hash') | ||||
|  | ||||
| 		ctemp = CONF.get('asics', ASICs(type).name, 'crit', 'temp') | ||||
| 		wtemp = CONF.get('asics', ASICs(type).name, 'warn', 'temp') | ||||
|  | ||||
| 		crej = CONF.get('asics', ASICs(type).name, 'crit', 'rej') | ||||
| 		wrej = CONF.get('asics', ASICs(type).name, 'warn', 'rej') | ||||
|  | ||||
| 		if rej > crej or ghs < chash or avg < chash or tmp > ctemp:		status = 'crit' | ||||
| 		elif rej > wrej or ghs < whash or avg < whash or tmp > wtemp:	status = 'warn' | ||||
|  | ||||
| 		DEBUG(f"{rej} | {ghs} | {avg} | {brd} | {tmp}") | ||||
|  | ||||
| 		return status | ||||
|  | ||||
|  | ||||
| 	def getstat_AntminerS19(self, log): | ||||
| 		status = 'ok' | ||||
|  | ||||
| 		rej = float(self.getreg('("Device Rejected%": ([0-9.]+),)', log)) | ||||
| 		ghs = int(float(self.getreg('("GHS 5s": ([0-9.]+),)', log))) | ||||
| 		avg = int(float(self.getreg('("GHS av": ([0-9.]+),)', log))) | ||||
| 		tmp = int(self.getreg('("TMax": ([0-9]+),)', log)) | ||||
|  | ||||
| 		chash = CONF.get('asics', ASICs(type).name, 'crit', 'hash') | ||||
| 		whash = CONF.get('asics', ASICs(type).name, 'warn', 'hash') | ||||
|  | ||||
| 		ctemp = CONF.get('asics', ASICs(type).name, 'crit', 'temp') | ||||
| 		wtemp = CONF.get('asics', ASICs(type).name, 'warn', 'temp') | ||||
|  | ||||
| 		crej = CONF.get('asics', ASICs(type).name, 'crit', 'rej') | ||||
| 		wrej = CONF.get('asics', ASICs(type).name, 'warn', 'rej') | ||||
|  | ||||
| 		if rej > crej or ghs < chash or avg < chash or tmp > ctemp:		status = 'crit' | ||||
| 		elif rej > wrej or ghs < whash or avg < whash or tmp > wtemp:	status = 'warn' | ||||
|  | ||||
| 		DEBUG(f"{rej} | {ghs} | {avg} | {brd} | {tmp}") | ||||
|  | ||||
| 		return status | ||||
|  | ||||
|  | ||||
| 	def gettype(self, ip): | ||||
| 		info = self.rtcp(ip, 4028, '{"command": "version"}') | ||||
|  | ||||
| 		if not info: | ||||
| 			return False | ||||
|  | ||||
| 		if CONF.get('debug') and info: | ||||
| 			info1 = self.rtcp(ip, 4028, '{"command": "stats"}') | ||||
| 			info2 = self.rtcp(ip, 4028, '{"command": "version"}') | ||||
| 			info3 = self.rtcp(ip, 4028, '{"command": "summary"}') | ||||
| 			info4 = self.rtcp(ip, 4028, '{"command": "pools"}') | ||||
| 			info5 = self.rtcp(ip, 4028, '{"command": "devdetails"}') | ||||
| 			info6 = self.rtcp(ip, 4028, '{"command": "get_version"}') | ||||
| 			info7 = self.rtcp(ip, 4028, '{"command": "status"}') | ||||
| 			info8 = self.rtcp(ip, 4028, '{"command": "get_miner_info"}') | ||||
| 			info9 = self.rtcp(ip, 4028, '{"command": "devs"}') | ||||
| 			info10 = self.rtcp(ip, 4028, '{"command": "notify"}') | ||||
| 			info11 = self.rtcp(ip, 4028, '{"command": "coin"}') | ||||
| 			info12 = self.rtcp(ip, 4028, '{"command": "edevs"}') | ||||
| 			info13 = self.rtcp(ip, 4028, '{"command": "estats"}') | ||||
|  | ||||
| 			DEBUG(f"[{ip}] {info1}") | ||||
| 			DEBUG(f"[{ip}] {info2}") | ||||
| 			DEBUG(f"[{ip}] {info3}") | ||||
| 			DEBUG(f"[{ip}] {info4}") | ||||
| 			DEBUG(f"[{ip}] {info5}") | ||||
| 			DEBUG(f"[{ip}] {info6}") | ||||
| 			DEBUG(f"[{ip}] {info7}") | ||||
| 			DEBUG(f"[{ip}] {info8}") | ||||
| 			DEBUG(f"[{ip}] {info9}") | ||||
| 			DEBUG(f"[{ip}] {info10}") | ||||
| 			DEBUG(f"[{ip}] {info11}") | ||||
| 			DEBUG(f"[{ip}] {info12}") | ||||
| 			DEBUG(f"[{ip}] {info13}") | ||||
|  | ||||
| 		info += self.rtcp(ip, 4028, '{"command": "devdetails"}') | ||||
|  | ||||
| 		# Bitmain | ||||
| 		if "Antminer" in info: | ||||
| 			if "S19J Pro" in info: | ||||
| 				return ASICs.AntminerS19JPro.value | ||||
| 			if "S19J" in info: | ||||
| 				return ASICs.AntminerS19J.value | ||||
| 			if "S19 Pro" in info: | ||||
| 				return ASICs.AntminerS19Pro.value | ||||
| 			if "S19" in info: | ||||
| 				return ASICs.AntminerS19.value | ||||
|  | ||||
| 			if "L3+" in info: | ||||
| 				return ASICs.AntminerL3plus.value | ||||
| 			if "L7" in info: | ||||
| 				return ASICs.AntminerL3plus.value | ||||
|  | ||||
| 			if "T17+" in info: | ||||
| 				return ASICs.AntminerT17plus.value | ||||
| 			if "T17" in info: | ||||
| 				return ASICs.AntminerT17.value | ||||
|  | ||||
| 		# Avalon | ||||
| 		if "Avalon" in info: | ||||
| 			if "1066" in info: | ||||
| 				return ASICs.Avalon1066.value | ||||
| 			if "1126" in info: | ||||
| 				return ASICs.Avalon1126.value | ||||
|  | ||||
| 		# WhatsMiner | ||||
| 		if "bitmicro" in info: | ||||
| 			if "M20s": | ||||
| 				return ASICs.WhatsMinerM20s.value | ||||
| 			if "M21s": | ||||
| 				return ASICs.WhatsMinerM21s.value | ||||
| 			if "M30": | ||||
| 				return ASICs.WhatsMinerM30.value | ||||
| 			if "M31": | ||||
| 				return ASICs.WhatsMinerM31.value | ||||
| 			if "M50": | ||||
| 				return ASICs.WhatsMinerM50.value | ||||
|  | ||||
| 		# Innosilicon | ||||
| 		if False: | ||||
| 			pass | ||||
|  | ||||
| 		return False | ||||
|  | ||||
|  | ||||
| 	def rtcp(self, ip, port, cmd): | ||||
| 		cmd = cmd.encode('utf-8') | ||||
| 		with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: | ||||
| 			try: | ||||
| 				s.connect((ip, port)) | ||||
| 				s.sendall(cmd) | ||||
| 				tdata = [] | ||||
| 				while 1: | ||||
| 					data = s.recv(1024*32) | ||||
| 					if not data: break | ||||
| 					tdata.append(data) | ||||
|  | ||||
| 				data = b''.join(tdata).decode("utf-8") | ||||
|  | ||||
| 				return data | ||||
| 			except OSError as e: | ||||
| 			#	ERR(e) | ||||
| 				return False | ||||
|  | ||||
|  | ||||
| 	def check_AntminerL3(self, ip): | ||||
| #		f'http://{ip}/cgi-bin/get_miner_status.cgi' | ||||
| #		f'http://{ip}/cgi-bin/get_miner_conf.cgi' | ||||
| 		summ = self.rtcp(ip, 4028, "summary") | ||||
| 		stat = self.rtcp(ip, 4028, "estats") | ||||
| 		info = self.rtcp(ip, 4028, "version") | ||||
|  | ||||
| 		if summ and stat: | ||||
| 			mac = json.loads(requests.get( | ||||
| 				f'http://{ip}/cgi-bin/get_system_info.cgi', | ||||
| 				auth=requests.auth.HTTPDigestAuth('root', 'root')).text)['macaddr'].lower() | ||||
|  | ||||
| 			log = f"{info} ||| {summ} ||| {stat}" | ||||
|  | ||||
| 			return log, mac | ||||
|  | ||||
| 		return False, False | ||||
|  | ||||
|  | ||||
| 	def check_AntminerS19(self, ip): | ||||
| #		f'http://{ip}/cgi-bin/get_miner_status.cgi' | ||||
| #		f'http://{ip}/cgi-bin/get_miner_conf.cgi' | ||||
| 		summ = self.rtcp(ip, 4028, "summary") | ||||
| 		stat = self.rtcp(ip, 4028, "stats") | ||||
| 		info = self.rtcp(ip, 4028, "version") | ||||
|  | ||||
| 		if summ and stat: | ||||
| 			mac = json.loads(requests.get( | ||||
| 				f'http://{ip}/cgi-bin/get_network_info.cgi', | ||||
| 				auth=requests.auth.HTTPDigestAuth('root', 'root')).text)['macaddr'].lower() | ||||
|  | ||||
| 			log = f"{info} ||| {summ} ||| {stat}" | ||||
|  | ||||
| 			return log, mac | ||||
|  | ||||
| 		return False, False | ||||
|  | ||||
|  | ||||
| 	def check_Avalon1XXX(self, ip): | ||||
| 		summ = self.rtcp(ip, 4028, "summary") | ||||
| 		stat = self.rtcp(ip, 4028, "estats") | ||||
| 		info = self.rtcp(ip, 4028, "version") | ||||
|  | ||||
| 		if summ and stat: | ||||
| 			mac = self.getreg(r'(MAC=([A-z0-9]+))', info) | ||||
| 			if mac: mac = ':'.join(mac[i:i+2] for i in range(0,12,2)) | ||||
| 			else: mac = ':' | ||||
|  | ||||
| 			log = f"{info} ||| {summ} ||| {stat}" | ||||
|  | ||||
| 			return log, mac | ||||
|  | ||||
| 		return False, False | ||||
|  | ||||
|  | ||||
| 	def check(self, ip): | ||||
| 		try: | ||||
| 			type = self.gettype(ip) | ||||
|  | ||||
| 			if not type: | ||||
| 				return (False, False, False, False, False), False | ||||
|  | ||||
| 			if type in [ASICs.AntminerL3plus.value]: | ||||
| 				log, mac = self.check_AntminerL3(ip) | ||||
| 				status = self.getstat_AntminerL3(log, type) | ||||
| 			elif type in [ASICs.Avalon1066.value, ASICs.Avalon1126.value]: | ||||
| 				log, mac = self.check_Avalon1XXX(ip) | ||||
| 				status = self.getstat_Avalon1XXX(log, type) | ||||
| 			elif type in [ASICs.AntminerS19.value, ASICs.AntminerS19J.value, ASICs.AntminerS19Pro.value, ASICs.AntminerS19JPro.value]: | ||||
| 				log, mac = self.check_AntminerS19(ip) | ||||
| 				status = self.getstat_AntminerS19(log, type) | ||||
|  | ||||
| 			return (ip, mac, type, int(time()), log), status | ||||
| 		except Exception as e: | ||||
| 			WARN('Minor error in <lambda>Thread: ' + str(e)) | ||||
| 			return (False, False, False, False, False), False | ||||
|  | ||||
|  | ||||
| 	def checkall(self): | ||||
| 		ips = [] | ||||
|  | ||||
| 		with self.conn.cursor(dictionary=True) as c: | ||||
| 			c.execute("SELECT * FROM `ips` WHERE `location` = %s", (CONF.get('location'),)) | ||||
|  | ||||
| 			for ip in c.fetchall(): | ||||
| 				ip = ip['ip'] | ||||
| 				if ip.isalpha() or not ip.find('-'): | ||||
| 					ips.append(ip) | ||||
| 				else: | ||||
| 					i_p = ip.split('.') | ||||
| 					i_p_new = [] | ||||
| 					for i in i_p: | ||||
| 						i_p_new.append(i.split('-')) | ||||
| 						i_p_new[-1][0] = int(i_p_new[-1][0]) | ||||
| 						i_p_new[-1][-1] = int(i_p_new[-1][-1]) | ||||
|  | ||||
| 					for i1 in range(i_p_new[0][0], i_p_new[0][-1] + 1): | ||||
| 						for i2 in range(i_p_new[1][0], i_p_new[1][-1] + 1): | ||||
| 							for i3 in range(i_p_new[2][0], i_p_new[2][-1] + 1): | ||||
| 								for i4 in range(i_p_new[3][0], i_p_new[3][-1] + 1): | ||||
| 									ips.append(f"{i1}.{i2}.{i3}.{i4}") | ||||
|  | ||||
| 		Tca = [] | ||||
| 		ca = [] | ||||
| 		for ip in ips: | ||||
| 			T = Thread(target=lambda x: ca.append(self.check(x)), args=(ip,)) | ||||
| 			Tca.append(T) | ||||
| 		for i in range(len(Tca)): | ||||
| 			Tca[i].start() | ||||
| 		for i in range(len(Tca)): | ||||
| 			Tca[i].join() | ||||
|  | ||||
| 		with self.conn.cursor() as c: | ||||
| 			c.execute(f"DELETE FROM `laststate` WHERE `location` = %s", (CONF.get('location'),)) | ||||
|  | ||||
| 			for i in ca: | ||||
| 				i, status = i | ||||
| 				if i[4]: | ||||
| 					c.execute(f"INSERT INTO `asiclogs` (`ip`, `mac`, `type`, `time`, `log`) VALUES (%s, %s, %s, %s, %s)", i) | ||||
|  | ||||
| 					if i[1] == ':': | ||||
| 						continue | ||||
| 					c.execute( | ||||
| 						f"INSERT INTO `laststate` (`ip`, `mac`, `type`, `location`, `status`, `time`) VALUES (%s, %s, %s, %s, %s, %s)", | ||||
| 						(i[0], i[1], i[2], CONF.get('location'), status, int(time()))) | ||||
|  | ||||
| 			self.conn.commit() | ||||
|  | ||||
|  | ||||
| 	def run(self): | ||||
| 		try: | ||||
| 			SUCC("Checker started!") | ||||
| 			sleep(5) | ||||
|  | ||||
| 			self.conn = connect( | ||||
| 				host=CONF.get('db', 'host'), | ||||
| 				user=CONF.get('db', 'user'), | ||||
| 				password=CONF.get('db', 'password'), | ||||
| 				database=CONF.get('db', 'name')) | ||||
|  | ||||
| 			while 1: | ||||
| 				if self.event.is_set(): | ||||
| 					SUCC("Checker stopped!") | ||||
| 					break | ||||
|  | ||||
| 				self.checkall() | ||||
| 				sleep(10) | ||||
| 		except Exception as e: | ||||
| 			CRIT(str(e)) | ||||
| 			os._exit(1) | ||||
							
								
								
									
										3
									
								
								checkmem
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										3
									
								
								checkmem
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,3 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| watch -n 0.1 'ps -o vsize,command,pcpu ax | sort -b -k3 -r | grep -E -i -w "[0-9]+ python"' | ||||
							
								
								
									
										43
									
								
								cleaner.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								cleaner.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| from threading import Thread | ||||
| from mysql.connector import connect, Error | ||||
| from time import sleep, time | ||||
| from macros import * | ||||
| import os | ||||
|  | ||||
|  | ||||
| class NThread(Thread): | ||||
| 	conn = None | ||||
|  | ||||
| 	def __init__(self, event): | ||||
| 		super(NThread, self).__init__() | ||||
| 		self.event = event | ||||
|  | ||||
|  | ||||
| 	def clean(self): | ||||
| 		with self.conn.cursor() as c: | ||||
| 			c.execute(f"DELETE FROM `asiclogs` WHERE `time` < UNIX_TIMESTAMP() - %s", (CONF.get('db-log-days'),)) | ||||
| 			c.execute(f"DELETE FROM `laststate` WHERE `time` < UNIX_TIMESTAMP() - 30") | ||||
|  | ||||
| 			self.conn.commit() | ||||
|  | ||||
|  | ||||
| 	def run(self): | ||||
| 		try: | ||||
| 			SUCC("Cleaner started!") | ||||
|  | ||||
| 			self.conn = connect( | ||||
| 				host=CONF.get('db', 'host'), | ||||
| 				user=CONF.get('db', 'user'), | ||||
| 				password=CONF.get('db', 'password'), | ||||
| 				database=CONF.get('db', 'name')) | ||||
|  | ||||
| 			while 1: | ||||
| 				if self.event.is_set(): | ||||
| 					SUCC('Cleaner stopped!') | ||||
| 					break | ||||
|  | ||||
| 				self.clean() | ||||
| 				sleep(5) | ||||
| 		except Exception as e: | ||||
| 			CRIT(str(e)) | ||||
| 			os._exit(1) | ||||
							
								
								
									
										20
									
								
								colors.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								colors.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| RESET     = "\033[0m" | ||||
| BOLD      = "\033[1m" | ||||
|  | ||||
| BLACK     = "\033[30m" | ||||
| RED       = "\033[31m" | ||||
| GREEN     = "\033[32m" | ||||
| YELLOW    = "\033[33m" | ||||
| BLUE      = "\033[34m" | ||||
| MAGENTA   = "\033[35m" | ||||
| CYAN      = "\033[36m" | ||||
| WHITE     = "\033[37m" | ||||
|  | ||||
| BGBLACK   = "\033[40m" | ||||
| BGRED     = "\033[41m" | ||||
| BGGREEN   = "\033[42m" | ||||
| BGYELLOW  = "\033[43m" | ||||
| BGBLUE    = "\033[44m" | ||||
| BGMAGENTA = "\033[45m" | ||||
| BGCYAN    = "\033[46m" | ||||
| BGWHITE   = "\033[47m" | ||||
							
								
								
									
										73
									
								
								config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,73 @@ | ||||
| { | ||||
| 	"license": "00001111-2222-3333-4444-555566667777", | ||||
| 	"location": "Underground", | ||||
| 	"logging": true, | ||||
| 	"db-log-days": 7, | ||||
| 	"debug": true, | ||||
| 	"updates": false, | ||||
| 	"port": 9070, | ||||
| 	"telegram": { | ||||
| 		"enable": true, | ||||
| 		"channel": -1001871940722, | ||||
| 		"token": "5953600362:AAELgZr0Ldstf0omK43zKrAzGXOuMGowcf8", | ||||
| 		"normal-notify": true, | ||||
| 		"warn-notify": false, | ||||
| 		"crit-notify": true | ||||
| 	}, | ||||
| 	"db": { | ||||
| 		"host": "127.0.0.1", | ||||
| 		"user": "root", | ||||
| 		"password": "0", | ||||
| 		"name": "myasics" | ||||
| 	}, | ||||
| 	"asics": { | ||||
| 		"L3plus": { | ||||
| 			"crit": { | ||||
| 				"hash": 40000, | ||||
| 				"temp": 90, | ||||
| 				"rejects": 2.5 | ||||
| 			}, | ||||
| 			"warn": { | ||||
| 				"hash": 45000, | ||||
| 				"temp": 80, | ||||
| 				"rejects": 1.5 | ||||
| 			} | ||||
| 		}, | ||||
| 		"S19": { | ||||
| 			"crit": { | ||||
| 				"hash": 80000, | ||||
| 				"temp": 90, | ||||
| 				"rejects": 2.5 | ||||
| 			}, | ||||
| 			"warn": { | ||||
| 				"hash": 85000, | ||||
| 				"temp": 80, | ||||
| 				"rejects": 1.5 | ||||
| 			} | ||||
| 		}, | ||||
| 		"Avalon1066": { | ||||
| 			"crit": { | ||||
| 				"hash": 42000, | ||||
| 				"temp": 90, | ||||
| 				"rejects": 2.5 | ||||
| 			}, | ||||
| 			"warn": { | ||||
| 				"hash": 46000, | ||||
| 				"temp": 80, | ||||
| 				"rejects": 1.5 | ||||
| 			} | ||||
| 		}, | ||||
| 		"Avalon1126": { | ||||
| 			"crit": { | ||||
| 				"hash": 64000, | ||||
| 				"temp": 90, | ||||
| 				"rejects": 2.5 | ||||
| 			}, | ||||
| 			"warn": { | ||||
| 				"hash": 66000, | ||||
| 				"temp": 80, | ||||
| 				"rejects": 1.5 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										3
									
								
								config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								config.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| APPNAME='Bit.ASICmon' | ||||
| VERSION='v0.1.1a' | ||||
| DEBUGGING=True | ||||
							
								
								
									
										12
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| version: '3.1' | ||||
|  | ||||
| services: | ||||
|  | ||||
|   db: | ||||
|     image: mysql | ||||
|     command: --default-authentication-plugin=mysql_native_password | ||||
|     restart: always | ||||
|     environment: | ||||
|       MYSQL_ROOT_PASSWORD: 0 | ||||
|     ports: | ||||
|       - 3306:3306 | ||||
							
								
								
									
										223
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,223 @@ | ||||
| const sprintf = (...[string, ...args]) => { | ||||
|     return string.replace(/{(\d+)}/g, (match, number) => args[number] ?? match); | ||||
| } | ||||
|  | ||||
| const getJSON = async url => { | ||||
| 	const resp = await fetch(url) | ||||
| 	const json = await resp.json() | ||||
|     return json; | ||||
| } | ||||
|  | ||||
| const createPopup = async id => { | ||||
| 	const asics = await getJSON('/api/asictypes') | ||||
|  | ||||
| 	asicoptions = '' | ||||
| 	asics.forEach(elem => { | ||||
| 		asicoptions += sprintf('<option value="{0}">{1}</option>', elem['key'], elem['value']) | ||||
| 	}) | ||||
|  | ||||
| 	document.querySelector('body').innerHTML += sprintf(popup, id, asicoptions, 'content') | ||||
| } | ||||
|  | ||||
| const deletePopup = id => { | ||||
| 	document.querySelector('#popup-' + id).remove() | ||||
| } | ||||
|  | ||||
| const update = async () => { | ||||
| 	let inner = await getStats() | ||||
| 	document.querySelector('.grid').innerHTML = inner | ||||
| } | ||||
|  | ||||
| const datafn = async () => { | ||||
| 	// return await getJSON('/api/webinit') | ||||
| } | ||||
|  | ||||
| const data = datafn() | ||||
|  | ||||
| console.log(data) | ||||
|  | ||||
| const cols = 40 | ||||
| const rows = 40 | ||||
|  | ||||
|  | ||||
| const css = | ||||
| 	`<style> | ||||
| 	@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;500;600;700&display=swap'); | ||||
|  | ||||
| 	* { | ||||
| 		margin: 0; | ||||
| 		padding: 0; | ||||
| 		box-sizing: border-box; | ||||
| 		font-family: 'Source Code Pro', monospace; | ||||
| 	} | ||||
|  | ||||
| 	body { | ||||
| 		background: #000; | ||||
| 	} | ||||
|  | ||||
| 	input, | ||||
| 	select { | ||||
| 		border: 1px solid #fff; | ||||
| 		background: #000; | ||||
| 		color: #fff; | ||||
| 		padding: 5px 10px; | ||||
| 	} | ||||
|  | ||||
| 	.green { | ||||
| 		background: #0b0 !important; | ||||
| 	} | ||||
|  | ||||
| 	.yellow { | ||||
| 		background: #ff0 !important; | ||||
| 	} | ||||
|  | ||||
| 	.red { | ||||
| 		background: #f11 !important; | ||||
| 	} | ||||
|  | ||||
| 	.darkred { | ||||
| 		background: #300 !important; | ||||
| 	} | ||||
|  | ||||
| 	.header { | ||||
| 		height: 60px; | ||||
| 		background: #222; | ||||
| 		text-align: center; | ||||
| 		margin: 0 0 1px 0; | ||||
| 		display: grid; | ||||
| 		justify-items: center; | ||||
| 		align-content: center; | ||||
| 	} | ||||
|  | ||||
| 	.header-info { | ||||
| 		color: #bbb; | ||||
| 		font-weight: 700; | ||||
| 	} | ||||
|  | ||||
| 	.grid { | ||||
| 		display: grid; | ||||
| 		grid-template-columns: repeat(${cols}, 30px); | ||||
| 		grid-template-rows: repeat(${rows}, 30px); | ||||
| 		grid-gap: 1px; | ||||
| 		margin: auto; | ||||
| 		width: max-content; | ||||
| 	} | ||||
|  | ||||
| 	.cell { | ||||
| 		position: relative; | ||||
| 		background: #111; | ||||
| 		font-size: 9px; | ||||
| 	} | ||||
|  | ||||
| 	.cell:hover { | ||||
| 		cursor: pointer; | ||||
| 	} | ||||
|  | ||||
| 	.cell:hover::after { | ||||
| 		content: ''; | ||||
| 		position: absolute; | ||||
| 		top: 0; | ||||
| 		left: 0; | ||||
| 		width: 100%; | ||||
| 		height: 100%; | ||||
| 		pointer-events: none; | ||||
| 		background: #ffffff33; | ||||
| 	} | ||||
|  | ||||
| 	.popup { | ||||
| 		position: fixed; | ||||
| 		top: 0; | ||||
| 		left: 0; | ||||
| 		width: 100vw; | ||||
| 		height: 100vh; | ||||
| 		display: grid; | ||||
| 		justify-items: center; | ||||
| 		align-content: center; | ||||
| 	} | ||||
|  | ||||
| 	.popup-bg { | ||||
| 		position: absolute; | ||||
| 		width: 100%; | ||||
| 		height: 100%; | ||||
| 		background: #00000033; | ||||
| 		z-index: 98; | ||||
| 	} | ||||
|  | ||||
| 	.popup-window { | ||||
| 		position: relative; | ||||
| 		height: max-content; | ||||
| 		max-height: 90vh; | ||||
| 		width: max-content; | ||||
| 		max-width: 90vw; | ||||
| 		padding: 40px; | ||||
| 		background: #222; | ||||
| 		z-index: 99; | ||||
| 	} | ||||
| 	</style> | ||||
| 	` | ||||
|  | ||||
| const header = | ||||
| 	'<div class="header">' | ||||
| 		+ '<div class="header-info">' | ||||
| 			+ 'Всего устройств: {0}, Предупреждений, {1}, Ошибки: {2}, В сети: {3}, Не в сети: {4}' | ||||
| 		+ '</div>' | ||||
| 	+ '</div>' | ||||
|  | ||||
|  | ||||
| const cell = | ||||
| 	'<div class="cell {1}" id="cell-{0}" onclick="createPopup({0})">' | ||||
| 		+ '{2}' | ||||
| 	+ '</div>' | ||||
|  | ||||
| const grid = | ||||
| 	'<div class="grid">' | ||||
| 		+ '{0}' | ||||
| 	+ '</div>' | ||||
|  | ||||
| const popup = | ||||
| 	'<div class="popup" id="popup-{0}">' | ||||
| 		+ '<div class="popup-bg" onclick="deletePopup({0})"></div>' | ||||
| 		+ '<div class="popup-window">' | ||||
| 			+ '<div class="row">' | ||||
| 				+ '<select>' | ||||
| 					+ '<option value="none">Свободно</option>' | ||||
| 					+ '{1}' | ||||
| 				+ '</select>' | ||||
| 				+ '<input type="text" placeholder="Адрес...">' | ||||
| 			+ '</div>' | ||||
| 			+ '{2}' | ||||
| 		+ '</div>' | ||||
| 	+ '</div>' | ||||
|  | ||||
| const getStats = async () => { | ||||
| 	let cells = '' | ||||
| 	let info = await getJSON('/curstatus.json') | ||||
| 	for(let i = 1; i <= cols * rows; i++) { | ||||
| 		if(info.asics[i]?.status == "ok") | ||||
| 			cells += sprintf(cell, i, 'green', info.asics[i]?.hashrate) | ||||
| 		else if(info.asics[i]?.status == "warn") | ||||
| 			cells += sprintf(cell, i, 'yellow', info.asics[i]?.hashrate) | ||||
| 		else if(info.asics[i]?.status == "crit") | ||||
| 			cells += sprintf(cell, i, 'red', info.asics[i]?.hashrate) | ||||
| 		else if(info.asics[i]?.status == "off") | ||||
| 			cells += sprintf(cell, i, 'darkred', '') | ||||
| 		else | ||||
| 			cells += sprintf(cell, i, '', '') | ||||
| 	} | ||||
| 	return cells | ||||
| } | ||||
|  | ||||
| const run = async () => { | ||||
| 	document.querySelector('body').innerHTML += css | ||||
| 	document.querySelector('body').innerHTML += sprintf(header) | ||||
|  | ||||
| 	let inner = sprintf(grid, await getStats()) | ||||
| 	document.querySelector('body').innerHTML += inner | ||||
|  | ||||
| 	setInterval(async () => { | ||||
| 		await update() | ||||
| 	}, 10000) | ||||
| } | ||||
|  | ||||
|  | ||||
| run() | ||||
							
								
								
									
										76
									
								
								license.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								license.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | ||||
| from threading import Thread | ||||
| from time import sleep, time | ||||
| from macros import * | ||||
| from Crypto.Cipher import AES | ||||
| from Crypto.Random import get_random_bytes | ||||
| from hashlib import sha256 | ||||
| from base64 import b64decode | ||||
| from datetime import datetime | ||||
| from urllib import request | ||||
| import os, json, requests | ||||
|  | ||||
|  | ||||
| class LThread(Thread): | ||||
| 	_FIRST = True | ||||
|  | ||||
|  | ||||
| 	def __init__(self, event): | ||||
| 		super(LThread, self).__init__() | ||||
| 		self.event = event | ||||
|  | ||||
|  | ||||
| 	def check(self): | ||||
| 		url = 'https://license.bitheaven.ru/api/v1/license.check' | ||||
| 		post = { | ||||
| 			'key': CONF.get('license'), | ||||
| 			'loc': CONF.get('location'), | ||||
| 			'time': int(time()), | ||||
| 			'data': get_random_bytes(16) | ||||
| 		} | ||||
| 		curtime = post['time'] | ||||
|  | ||||
| 		try: | ||||
| 			req = requests.post(url, data=post).json() | ||||
| 		except requests.exceptions.RequestException as e: | ||||
| 			return False | ||||
|  | ||||
| 		if req['error']: | ||||
| 			ERR(f"Server error! {req['msg']}") | ||||
| 			return True | ||||
|  | ||||
| 		key = sha256(post['loc'].encode('utf-8')).hexdigest()[0:32].encode("utf-8") | ||||
| 		iv = post['data'] | ||||
| 		aes = AES.new(key, AES.MODE_OFB, iv=iv) | ||||
| 		msg = aes.decrypt(b64decode(req['data'])).decode('unicode_escape').strip() | ||||
|  | ||||
| 		until = datetime.fromtimestamp(req['until']).strftime('%d.%m.%Y') | ||||
|  | ||||
| 		if self._FIRST: | ||||
| 			INFO(f'License paid until: {until}') | ||||
| 			self._FIRST = False | ||||
|  | ||||
| 		return f"{CONF.get('license')}{curtime}" == msg | ||||
|  | ||||
|  | ||||
| 	def run(self): | ||||
| 		try: | ||||
| 			while 1: | ||||
| 				if self.event.is_set(): | ||||
| 					break | ||||
|  | ||||
| 				cur = self.check() | ||||
|  | ||||
| 				if not cur: | ||||
| 					try: | ||||
| 						request.urlopen('http://1.1.1.1', timeout=2) | ||||
| 						CRIT("License wrong or expired!") | ||||
| 						WARN("Get it on https://bhev.ru/glfbam") | ||||
| 						sleep(60) | ||||
| 					except: | ||||
| 						ERR("No access to network!") | ||||
| #					os._exit(1) | ||||
|  | ||||
| 				sleep(5) | ||||
| 		except Exception as e: | ||||
| 			CRIT(str(e)) | ||||
| 			os._exit(1) | ||||
							
								
								
									
										
											BIN
										
									
								
								log/.2023-05-10.log.swp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								log/.2023-05-10.log.swp
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										97
									
								
								macros.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								macros.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | ||||
| from datetime import datetime | ||||
| from colors import * | ||||
| from platform import system | ||||
| import inspect | ||||
| import json, os, config | ||||
|  | ||||
|  | ||||
| __DEBUGGING_ALERT = False | ||||
|  | ||||
| MUSTDIE = system().lower() == 'windows' | ||||
| LINUX = system().lower() == 'linux' | ||||
| MACOS = system().lower() == 'darwin' | ||||
|  | ||||
|  | ||||
| class CONF(): | ||||
| 	def __init__(self): | ||||
| 		CONF.config = json.load(open('config.json')) | ||||
|  | ||||
|  | ||||
| 	@staticmethod | ||||
| 	def get(*params): | ||||
| 		conf = CONF.config | ||||
|  | ||||
| 		for param in params: | ||||
| 			if param in conf.keys(): | ||||
| 				conf = conf[param] | ||||
|  | ||||
| 		if conf: | ||||
| 			return conf | ||||
|  | ||||
| 		return False | ||||
|  | ||||
| CONF() | ||||
|  | ||||
|  | ||||
| def __GET_TIME(): | ||||
| 	return datetime.now().strftime("%H:%M:%S.%f") | ||||
|  | ||||
|  | ||||
| def __GET_DATE(): | ||||
| 	return datetime.now().strftime("%Y-%m-%d") | ||||
|  | ||||
|  | ||||
| def __ADD_TO_LOG(str): | ||||
| 	if CONF.get('logging'): | ||||
| 		if not os.path.isdir('log'): | ||||
| 			os.mkdir('log') | ||||
| 		log = open(f'log/{__GET_DATE()}.log', 'a') | ||||
| 		log.write(str + '\n') | ||||
| 		log.close() | ||||
|  | ||||
|  | ||||
| def __PRINT_LOG(str, log): | ||||
| 	if CONF.get('debug') and config.DEBUGGING: | ||||
| 		st = inspect.stack()[2] | ||||
| 		caller = st.filename.split('/')[-1].split('.')[0] | ||||
| 		callerline = st.lineno | ||||
| 		str = f'[{__GET_TIME()}] [{caller}:{callerline}] {str}' | ||||
| 		log = f'[{caller}:{callerline}] {log}' | ||||
| 	else: | ||||
| 		str = f'[{__GET_TIME()}] {str}' | ||||
|  | ||||
| 	print(str) | ||||
| 	__ADD_TO_LOG(log) | ||||
|  | ||||
|  | ||||
| def INFO(s = ''): | ||||
| 	__PRINT_LOG(f"[INFO] {str(s)}{RESET}", str(s)) | ||||
|  | ||||
|  | ||||
| def DEBUG(s = ''): | ||||
| 	global __DEBUGGING_ALERT | ||||
|  | ||||
| 	if not CONF.get('debug'): | ||||
| 		return None | ||||
|  | ||||
| 	if config.DEBUGGING: | ||||
| 		__PRINT_LOG(f"{MAGENTA}[DEBUG]{RESET} {BGBLUE}{YELLOW}{BOLD}{str(s)}{RESET}", str(s)) | ||||
| 	elif not __DEBUGGING_ALERT: | ||||
| 		WARN('DEBUGGING DISABLED BY OWNER!') | ||||
| 		__DEBUGGING_ALERT = True | ||||
|  | ||||
|  | ||||
| def SUCC(s = ''): | ||||
| 	__PRINT_LOG(f"{GREEN}[SUCCESS]{RESET} {GREEN}{str(s)}{RESET}", str(s)) | ||||
|  | ||||
|  | ||||
| def WARN(s = ''): | ||||
| 	__PRINT_LOG(f"{YELLOW}[WARN]{RESET} {YELLOW}{str(s)}{RESET}", str(s)) | ||||
|  | ||||
|  | ||||
| def ERR(s = ''): | ||||
| 	__PRINT_LOG(f"{RED}[ERROR]{RESET} {RED}{str(s)}{RESET}", str(s)) | ||||
|  | ||||
|  | ||||
| def CRIT(s = ''): | ||||
| 	__PRINT_LOG(f"{RED}{BOLD}[CRITICAL]{RESET} {BGRED}{WHITE} {str(s)} {RESET}", str(s)) | ||||
							
								
								
									
										95
									
								
								main.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										95
									
								
								main.py
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,95 @@ | ||||
| #!/usr/bin/python | ||||
|  | ||||
| from updater import UThread | ||||
| from license import LThread | ||||
| from checker import CThread | ||||
| from cleaner import NThread | ||||
| from telegrambot import TGThread | ||||
| from webserver import WSThread | ||||
| from time import sleep | ||||
| from macros import * | ||||
| from colors import * | ||||
| from threading import Event | ||||
| import config, sys, os, asyncio | ||||
|  | ||||
|  | ||||
| INFO('╔' + '═'*(len(config.APPNAME) + len(config.VERSION) + 3) + '╗') | ||||
| INFO(f"║ {CYAN}{BOLD}{config.APPNAME}{RESET} {config.VERSION} ║") | ||||
| INFO('╚' + '═'*(len(config.APPNAME) + len(config.VERSION) + 3) + '╝') | ||||
|  | ||||
| if CONF.get('debug'): | ||||
| 	INFO('DEBUG TEST START ' + '*' * 20) | ||||
| 	DEBUG(f"{RESET}{BLACK}*{RED}*{GREEN}*{YELLOW}*{BLUE}*{MAGENTA}*{CYAN}*{WHITE}*") | ||||
| 	DEBUG(f"{RESET}{BOLD}{BLACK}*{RED}*{GREEN}*{YELLOW}*{BLUE}*{MAGENTA}*{CYAN}*{WHITE}*") | ||||
| 	DEBUG(f"{RESET}{BGBLACK}*{BGRED}*{BGGREEN}*{BGYELLOW}*{BGBLUE}*{BGMAGENTA}*{BGCYAN}*{BGWHITE}*") | ||||
| 	INFO('Test message') | ||||
| 	DEBUG('Test message') | ||||
| 	SUCC('Test message') | ||||
| 	WARN('Test message') | ||||
| 	ERR('Test message') | ||||
| 	CRIT('Test message') | ||||
| 	INFO('DEBUG TEST END ' + '*' * 22 + RESET) | ||||
|  | ||||
|  | ||||
| eupdate = Event() | ||||
| estop = Event() | ||||
|  | ||||
| lt = LThread(eupdate) | ||||
| lt.daemon = True | ||||
| lt.start() | ||||
|  | ||||
| wt = WSThread(eupdate) | ||||
| wt.daemon = True | ||||
| wt.start() | ||||
|  | ||||
| ct = CThread(eupdate) | ||||
| ct.daemon = True | ||||
| ct.start() | ||||
|  | ||||
| nt = NThread(eupdate) | ||||
| nt.daemon = True | ||||
| nt.start() | ||||
|  | ||||
| tt = TGThread(eupdate, estop) | ||||
| tt.daemon = True | ||||
| tt.start() | ||||
|  | ||||
| ut = UThread() | ||||
| ut.daemon = True | ||||
| ut.start() | ||||
|  | ||||
| updated = False | ||||
|  | ||||
| try: | ||||
| 	while 1: | ||||
| 		if lt.is_alive() and wt.is_alive() and ct.is_alive() and tt.is_alive() and ut.is_alive() and nt.is_alive(): | ||||
| 			sleep(1) | ||||
| 		elif lt.is_alive() and wt.is_alive() and ct.is_alive() and tt.is_alive() and nt.is_alive(): | ||||
| 			eupdate.set() | ||||
| 			if lt.is_alive(): lt.join() | ||||
| 			if wt.is_alive(): wt.join() | ||||
| 			if ct.is_alive(): ct.join() | ||||
| 			if nt.is_alive(): nt.join() | ||||
| 			if tt.is_alive(): tt.join() | ||||
| 			updated = True | ||||
| 			SUCC('Program updated, reboot!') | ||||
| 			break | ||||
| 		else: | ||||
| 			estop.set() | ||||
| 			sleep(1) | ||||
| 			CRIT('Program broken.') | ||||
| 			os._exit(3) | ||||
| except KeyboardInterrupt: | ||||
| 	sys.stdout.flush() | ||||
| 	print('\r', end='') | ||||
| 	estop.set() | ||||
| 	sleep(1) | ||||
| 	CRIT('STOP') | ||||
| except Exception as e: | ||||
| 	CRIT(str(e)) | ||||
| 	estop.set() | ||||
| 	sleep(1) | ||||
| 	os._exit(1) | ||||
|  | ||||
| if updated: | ||||
| 	os.execl(sys.argv[0], *sys.argv) | ||||
							
								
								
									
										4
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| flask | ||||
| python-telegram-bot | ||||
| pyinstaller | ||||
| pyarmor<8.0.0 | ||||
							
								
								
									
										125
									
								
								telegrambot.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								telegrambot.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,125 @@ | ||||
| from threading import Thread | ||||
| from telegram import Bot, error | ||||
| from asics import ASICs | ||||
| from time import sleep | ||||
| from mysql.connector import connect, Error | ||||
| from macros import * | ||||
| import os, json, asyncio | ||||
|  | ||||
|  | ||||
| class TGThread(Thread): | ||||
| 	conn = None | ||||
| 	bot = None | ||||
| 	asics = {} | ||||
|  | ||||
|  | ||||
| 	def __init__(self, event, event2): | ||||
| 		super(TGThread, self).__init__() | ||||
| 		self.event = event | ||||
| 		self.event2 = event2 | ||||
|  | ||||
|  | ||||
| 	async def sendmsg(self, msg): | ||||
| 		return await self.bot.send_message(CONF.get('telegram', 'channel'), msg, 'MarkdownV2') | ||||
|  | ||||
|  | ||||
| 	async def check(self): | ||||
| 		msg = [] | ||||
|  | ||||
| 		for i in self.asics.keys(): | ||||
| 			self.asics[i]['tooff'] -= 1 | ||||
|  | ||||
| 		with self.conn.cursor(dictionary=True) as c: | ||||
| 			c.execute(f"SELECT * FROM `laststate`") | ||||
|  | ||||
| 			for i in c.fetchall(): | ||||
| 				if not i['mac'] in self.asics.keys(): | ||||
| 					self.asics[i['mac']] = {} | ||||
| 					self.asics[i['mac']]['alert'] = 0 | ||||
|  | ||||
| 					msg.append(f"🟢 {i['location']} \| ASIC \"*{ASICs(i['type']).name}*\" `{i['ip']}` online\!") | ||||
|  | ||||
| 				self.asics[i['mac']]['tooff']  = 3 | ||||
| 				self.asics[i['mac']]['status'] = i['status'] | ||||
|  | ||||
| 				if i['status'] == 'crit': | ||||
| 					if CONF.get('telegram', 'crit-notify') and self.asics[i['mac']]['alert'] < 2: | ||||
| 						msg.append(f"🔴 {i['location']} \| ASIC \"*{ASICs(i['type']).name}*\" `{i['ip']}` crit status\!") | ||||
| 					self.asics[i['mac']]['alert'] = 2 | ||||
| 				elif i['status'] == 'warn': | ||||
| 					if CONF.get('telegram', 'warn-notify') and self.asics[i['mac']]['alert'] < 1: | ||||
| 						msg.append(f"🟡 {i['location']} \| ASIC \"*{ASICs(i['type']).name}*\" `{i['ip']}` warn status\!") | ||||
| 					self.asics[i['mac']]['alert'] = 1 | ||||
| 				elif i['status'] == 'ok': | ||||
| 					if CONF.get('telegram', 'normal-notify') and self.asics[i['mac']]['alert'] != 0: | ||||
| 						msg.append(f"🟢 {i['location']} \| ASIC \"*{ASICs(i['type']).name}*\" `{i['ip']}` normal status\!") | ||||
| 					self.asics[i['mac']]['alert'] = 0 | ||||
|  | ||||
| 				if self.asics[i['mac']]['tooff'] <= 0: | ||||
| 					msg.append(f"🔴 {i['location']} \| ASIC \"*{ASICs(i['type']).name}*\" `{i['ip']}` offline\!") | ||||
| 					del self.asics[i['mac']] | ||||
|  | ||||
| 		if len(msg) > 0: | ||||
| 			await self.sendmsg('\n'.join(msg)) | ||||
|  | ||||
|  | ||||
| 	async def runbot(self): | ||||
| 		try: | ||||
| 			res = await self.sendmsg('🟢 *Notify server online\!*') | ||||
| 			INFO(f"Telegram channel ID: {res['chat']['id']}") | ||||
|  | ||||
| 			while 1: | ||||
| 				if self.event.is_set(): | ||||
| 					SUCC('Telegram stopped!') | ||||
| 					await self.sendmsg('🟡 Updating software\! Recommend check all running instances.') | ||||
| 					break | ||||
|  | ||||
| 				await self.check() | ||||
| 				await asyncio.sleep(15) | ||||
|  | ||||
| 		except error.Forbidden as e: | ||||
| 			WARN(str(e)) | ||||
| 		except error.TimedOut as e: | ||||
| 			WARN(str(e)) | ||||
| 		except Exception as e: | ||||
| 			CRIT(str(e)) | ||||
| #			os._exit(1) | ||||
|  | ||||
|  | ||||
| 	async def stopalert(self): | ||||
| 		while 1: | ||||
| 			if self.event2.is_set(): | ||||
| 				await self.sendmsg('🔴 *Notify server offline\!*') | ||||
|  | ||||
| 			await asyncio.sleep(0.5) | ||||
|  | ||||
|  | ||||
| 	async def main(self): | ||||
| 		await asyncio.gather(self.stopalert(), self.runbot()) | ||||
|  | ||||
|  | ||||
| 	def run(self): | ||||
| 		try: | ||||
| 			if CONF.get('telegram', 'enable'): | ||||
| 				SUCC('Telegram started!') | ||||
|  | ||||
| 				self.conn = connect( | ||||
| 					host=CONF.get('db', 'host'), | ||||
| 					user=CONF.get('db', 'user'), | ||||
| 					password=CONF.get('db', 'password'), | ||||
| 					database=CONF.get('db', 'name')) | ||||
| 				self.conn.autocommit = True | ||||
|  | ||||
| 				self.bot = Bot(CONF.get('telegram', 'token')) | ||||
|  | ||||
| 				asyncio.run(self.main()) | ||||
| 			else: | ||||
| 				while 1: | ||||
| 					if self.event.is_set(): | ||||
| 						SUCC('Telegram stopped!') | ||||
| 						break | ||||
|  | ||||
| 					sleep(5) | ||||
| 		except Exception as e: | ||||
| 			CRIT(str(e)) | ||||
| 			os._exit(1) | ||||
							
								
								
									
										54
									
								
								updater.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								updater.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| from threading import Thread | ||||
| from macros import * | ||||
| from config import * | ||||
| from time import sleep | ||||
| import urllib, os, requests | ||||
|  | ||||
| class UThread(Thread): | ||||
| 	path = os.path.dirname(__file__) | ||||
|  | ||||
| 	def __init__(self): | ||||
| 		super(UThread, self).__init__() | ||||
|  | ||||
|  | ||||
| 	def run(self): | ||||
| 		global VERSION | ||||
|  | ||||
| 		try: | ||||
| 			SUCC('Updater started!') | ||||
| 			while 1: | ||||
| 				if not CONF.get('updates'): | ||||
| 					sleep(1) | ||||
| 					continue | ||||
|  | ||||
| 				version = requests.get('https://mirror.bitheaven.ru/main/versions/Bit.ASICmon-a').text | ||||
|  | ||||
| 				if version == VERSION: | ||||
| 					sleep(300) | ||||
| 					continue | ||||
|  | ||||
| 				INFO('Update found! Downloading...') | ||||
| 				if LINUX: | ||||
| 					with urllib.request.urlopen("https://mirror.bitheaven.ru/main/archive/Bit.ASICmon-a_linux") as upd: | ||||
| 						with open(self.path, "wb+") as f: | ||||
| 							INFO('Installing update...') | ||||
| 							f.write(upd.read()) | ||||
| 							INFO('Stopping process...') | ||||
| 							break | ||||
| 				elif MACOS: | ||||
| 					with urllib.request.urlopen("https://mirror.bitheaven.ru/main/archive/Bit.ASICmon-a_macos") as upd: | ||||
| 						with open(self.path, "wb+") as f: | ||||
| 							INFO('Installing update...') | ||||
| 							f.write(upd.read()) | ||||
| 							INFO('Stopping process...') | ||||
| 							break | ||||
| 				elif MUSTDIE: | ||||
| 					with urllib.request.urlopen("https://mirror.bitheaven.ru/main/archive/Bit.ASICmon-a_mustdie.exe") as upd: | ||||
| 						with open(self.path, "wb+") as f: | ||||
| 							INFO('Installing update...') | ||||
| 							f.write(upd.read()) | ||||
| 							INFO('Stopping process...') | ||||
| 							break | ||||
| 		except Exception as e: | ||||
| 			CRIT(str(e)) | ||||
| 			os._exit(1) | ||||
							
								
								
									
										107
									
								
								webserver.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								webserver.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | ||||
| from flask import Flask, send_file, request | ||||
| from time import sleep | ||||
| from werkzeug.serving import make_server | ||||
| from threading import Thread | ||||
| from mysql.connector import connect, Error | ||||
| from macros import * | ||||
| import os, json, config, requests, logging | ||||
|  | ||||
|  | ||||
| class WSThread(Thread): | ||||
| 	app = Flask(__name__) | ||||
| 	conn = None | ||||
|  | ||||
| 	def __init__(self, event): | ||||
| 		super(WSThread, self).__init__() | ||||
| 		self.event = event | ||||
|  | ||||
| 		log = logging.getLogger('werkzeug') | ||||
| 		log.disabled = True | ||||
|  | ||||
|  | ||||
| 	@app.route('/') | ||||
| 	@app.route('/index') | ||||
| 	def index(): | ||||
| 		return '<meta charset="UTF-8"><script defer src="/index.js"></script>' | ||||
|  | ||||
|  | ||||
| 	@app.route('/index.js') | ||||
| 	def indexjs(): | ||||
| 		return send_file('index.js') | ||||
|  | ||||
|  | ||||
| 	@app.route('/curstatus.json') | ||||
| 	def curstatus(): | ||||
| 		j = {} | ||||
| 		j['info'] = {} | ||||
| 		j['info']['asics'] = {} | ||||
|  | ||||
| 		return json.dumps(j), 200, {'ContentType':'application/json'} | ||||
|  | ||||
|  | ||||
| 	def dbinit(self): | ||||
| 		with self.conn.cursor() as c: | ||||
| 			c.execute(""" | ||||
| 				CREATE TABLE IF NOT EXISTS `ips` ( | ||||
| 					`id` INT AUTO_INCREMENT PRIMARY KEY, | ||||
| 					`ip` VARCHAR(64) NOT NULL, | ||||
| 					`location` VARCHAR(64) NOT NULL | ||||
| 				) | ||||
| 			""") | ||||
| 			c.execute(""" | ||||
| 				CREATE TABLE IF NOT EXISTS `laststate` ( | ||||
| 					`id` INT AUTO_INCREMENT PRIMARY KEY, | ||||
| 					`ip` VARCHAR(64) NOT NULL, | ||||
| 					`mac` VARCHAR(32) NOT NULL, | ||||
| 					`type` INT(11) NOT NULL, | ||||
| 					`location` VARCHAR(64) NOT NULL, | ||||
| 					`status` VARCHAR(32) NOT NULL, | ||||
| 					`time` INT(11) NOT NULL | ||||
| 				) | ||||
| 			""") | ||||
| 			c.execute(""" | ||||
| 				CREATE TABLE IF NOT EXISTS `asiclogs` ( | ||||
| 					`id` INT AUTO_INCREMENT PRIMARY KEY, | ||||
| 					`ip` VARCHAR(64) NOT NULL, | ||||
| 					`mac` VARCHAR(64) NOT NULL, | ||||
| 					`type` VARCHAR(64) NOT NULL, | ||||
| 					`time` INT(11) NOT NULL, | ||||
| 					`log` TEXT NOT NULL | ||||
| 				) | ||||
| 			""") | ||||
|  | ||||
| 			self.conn.commit() | ||||
|  | ||||
|  | ||||
| 	def runweb(self): | ||||
| 		self.server = make_server('0.0.0.0', CONF.get('port'), self.app) | ||||
| 		self.server.serve_forever() | ||||
|  | ||||
|  | ||||
| 	def run(self): | ||||
| 		try: | ||||
| 			with connect(host=CONF.get('db', 'host'), user=CONF.get('db', 'user'), password=CONF.get('db', 'password')) as conn: | ||||
| 				with conn.cursor() as c: | ||||
| 					c.execute(f"CREATE DATABASE IF NOT EXISTS {CONF.get('db', 'name')}") | ||||
|  | ||||
| 			self.conn = connect( | ||||
| 				host=CONF.get('db', 'host'), | ||||
| 				user=CONF.get('db', 'user'), | ||||
| 				password=CONF.get('db', 'password'), | ||||
| 				database=CONF.get('db', 'name')) | ||||
| 			self.dbinit() | ||||
|  | ||||
| 			web = Thread(target=self.runweb) | ||||
| 			web.daemon = True | ||||
| 			web.start() | ||||
|  | ||||
| 			SUCC(f"Web interface started at port {CONF.get('port')}!") | ||||
|  | ||||
| 			while not self.event.is_set(): | ||||
| 				sleep(1) | ||||
|  | ||||
| 			SUCC(f"Web server stopped!") | ||||
| 			self.server.shutdown() | ||||
| 		except Exception as e: | ||||
| 			CRIT(str(e)) | ||||
| 			os._exit(1) | ||||
		Reference in New Issue
	
	Block a user